> ## Documentation Index
> Fetch the complete documentation index at: https://docs.digiflecttech.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Authenticate Save App API Requests Using JWT Tokens

> Save App uses JWT bearer tokens. Log in with a phone number and 4-digit PIN or Firebase Phone Auth, then pass the token in every authenticated request.

Every Save App API endpoint that reads or modifies group data requires authentication. You authenticate by exchanging credentials for a short-lived JWT token, then include that token in the `Authorization` header of each request. Save App supports two login methods: a direct phone/PIN login for testing and development, and Firebase Phone Authentication for production mobile apps.

## How authentication works

1. Your app sends a phone number and 4-digit PIN (or a Firebase ID token) to a login endpoint.
2. The API returns a JWT bearer token along with the user's name, role, and creator status.
3. Your app includes the token on every subsequent request as `Authorization: Bearer <token>`.
4. The token expires after **24 hours**. After expiry, your app must log in again to get a new token.

The token encodes the user's phone number as its `sub` claim. The API verifies the token on each request and loads the corresponding user record from the database.

<Warning>
  Tokens expire after 24 hours. Store the expiry time alongside the token in your app and refresh it before it expires to avoid interrupting the user's session.
</Warning>

## Login methods

### Phone and PIN login

Use `POST /api/auth/login` to log in with a phone number and 4-digit numeric PIN. Phone numbers must be in Uganda format (`+256XXXXXXXXX`). This method is rate-limited.

Pass `groupName` to ensure the user belongs to the correct savings group. Pass `loginType` (`"admin"` or `"member"`) to enforce role-based portal access — a member attempting admin login receives `403 Forbidden`.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST http://localhost:8000/api/auth/login \
    -H "Content-Type: application/json" \
    -d '{
      "phone": "+256701234567",
      "password": "1234",
      "groupName": "Kampala Savers",
      "loginType": "admin"
    }'
  ```
</CodeGroup>

```json theme={null}
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "name": "Amara Osei",
  "role": "admin",
  "is_creator": true
}
```

### Firebase Phone Authentication

Use `POST /api/auth/firebase-login` to log in with a Firebase ID token obtained after phone verification on the mobile client. This is the recommended flow for production apps because Firebase handles OTP delivery, SIM-swap detection, and phone number verification.

If the phone number does not already have an account, the API creates one automatically with `role: member`.

<Tip>
  Use Firebase Phone Auth in production. It handles OTP sending, rate limiting, and phone number verification natively, so your app does not need to manage that complexity.
</Tip>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST http://localhost:8000/api/auth/firebase-login \
    -H "Content-Type: application/json" \
    -d '{
      "idToken": "<firebase-id-token>",
      "group_name": "Kampala Savers"
    }'
  ```
</CodeGroup>

```json theme={null}
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "name": "Fatima Nakato",
  "role": "member",
  "is_creator": false
}
```

The `firebase-login` endpoint is rate-limited to 10 requests per minute per IP.

## Making authenticated requests

Include the token from the login response in the `Authorization` header of every request that requires authentication:

```
Authorization: Bearer <token>
```

<CodeGroup>
  ```bash curl theme={null}
  curl -X GET http://localhost:8000/api/analytics/summary \
    -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  ```
</CodeGroup>

Omitting the header or passing an invalid token returns `401 Unauthorized`.

## Login response fields

| Field        | Type    | Description                                                                                                                          |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `token`      | string  | JWT bearer token. Include in `Authorization: Bearer <token>` on every request.                                                       |
| `name`       | string  | Display name of the authenticated user.                                                                                              |
| `role`       | string  | Either `"admin"` or `"member"`. Determines which endpoints the user can access.                                                      |
| `is_creator` | boolean | `true` if this admin was the first to register under their group name. Creators have additional privileges over group configuration. |

## Error codes

| Code               | Meaning                                                                                                                                                |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `401 Unauthorized` | Missing or invalid token, wrong password, or account not found.                                                                                        |
| `403 Forbidden`    | Valid token but insufficient permissions — for example, a member attempting to access an admin-only endpoint, or a user logging in to the wrong group. |

## Member onboarding

When an admin adds a new member via `POST /api/members`, the member's account is created with `status: "pending"` and a temporary password. The member must complete onboarding before they can log in normally.

<Steps>
  <Step title="Check phone number">
    The member's app calls `POST /api/auth/onboarding/check-phone` to verify their phone number exists in the group as a pending account.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST http://localhost:8000/api/auth/onboarding/check-phone \
        -H "Content-Type: application/json" \
        -d '{
          "phone": "+256789876543",
          "groupName": "Kampala Savers"
        }'
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "success": true,
      "message": "User found"
    }
    ```

    If the phone number is not found as pending in the specified group, the response returns `success: false` with a message asking the member to contact their admin.
  </Step>

  <Step title="Set a password">
    The member calls `POST /api/auth/onboarding/set-password` to choose their PIN. This activates the account (`status` changes from `"pending"` to `"active"`) and returns a JWT token so the member is immediately logged in.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST http://localhost:8000/api/auth/onboarding/set-password \
        -H "Content-Type: application/json" \
        -d '{
          "phone": "+256789876543",
          "password": "5678"
        }'
      ```
    </CodeGroup>

    ```json theme={null}
    {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "name": "Fatima Nakato",
      "role": "member",
      "is_creator": false
    }
    ```

    After this step, the member can log in normally using `POST /api/auth/login`.
  </Step>
</Steps>

<Info>
  The onboarding endpoints normalize Ugandan phone numbers automatically. A number entered as `0789876543` is treated the same as `+256789876543`.
</Info>
