> For the complete documentation index, see [llms.txt](https://docs.january.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.january.ai/docs/authentication.md).

# How authentication works

January accepts two credentials. Both travel in the same header, `Authorization: Bearer …`:

| Credential            | Held by                          | What it can do                                                                                            |
| --------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------- |
| API key (`sk-…`)      | Your backend                     | Call every endpoint for your account.                                                                     |
| Client token (`ct-…`) | Your app, for one signed-in user | Act as that one user, within the scopes it was granted, for 5 minutes to 2 hours (30 minutes by default). |

These terms are used throughout the docs:

* **End user**: a person using your app.
* **End-user ID**: your own stable, opaque ID for that person, up to 64 characters (for example `acme-user-8271`). Never use an email address, a name, or other personal data. There is no registration call; any stable ID you choose works.
* **App session**: the login token or cookie between your app and your backend. January never sees it.

## API keys

Create a key in the [January Developer Dashboard](https://dashboard.january.ai) under **API keys → Create key**. The full value is shown once. Keep it in a secret manager or a server environment variable. Never put it in a browser bundle, a mobile app, a repository, a log, or a screenshot.

## Client tokens

An app on an end user's device never holds the API key. It asks your backend for a client token, then calls January directly with it:

```mermaid
sequenceDiagram
    autonumber
    participant App as Your app
    participant Backend as Your backend
    participant January as January API<br/>partners.january.ai

    Note over Backend: Holds your API key (sk-…).<br/>It never leaves your server.

    App->>Backend: POST /your-token-endpoint<br/>with the user's app session
    Note over Backend: Verify the session and<br/>look up that user's end-user ID
    Backend->>January: POST /v1.2/auth/client-tokens<br/>Authorization: Bearer sk-…<br/>{ end_user_id, scopes, ttl_seconds }
    January-->>Backend: 201 { token: "ct-…", expires_in: 1800, … }
    Backend-->>App: January's JSON, unchanged
    Note over App: The SDK keeps the token in memory<br/>and refreshes it before it expires

    loop Until the token expires
        App->>January: GET /v1.2/foods?query=…<br/>Authorization: Bearer ct-…
        January-->>App: Data for that user only
    end

    App->>January: A call after expiry
    January-->>App: 401 { code: "token_expired" }
    Note over App,Backend: Repeat steps 1 to 4, then retry the call once
```

1. **The app asks your backend for a token.** The request goes to an endpoint you add, authenticated like the rest of your API.
2. **Your backend mints the token** with its API key, for the user the session proves.
3. **January returns a `ct-…` token** bound to that user, with `expires_in` in seconds.
4. **Your backend returns January's JSON to the app unchanged.**
5. **The app calls January directly** with `Authorization: Bearer ct-…`. On `401 token_expired` it gets a new token and retries once. The SDKs do this for you.

Mobile apps can always call January directly. Browser apps can do so only from web origins January has enabled for your account; until then, call January from your backend. See [Web SDK runtime boundaries](/client-sdks/web-sdk/concepts/runtime-boundaries.md).

## Your token endpoint

The token endpoint is the only server code a client-token integration needs, and it is the same for every SDK.

1. **Authenticate the request** with your existing app session. Return `401` if there is none.
2. **Look up the end-user ID** for that session in your own records. Never take it from the request: not from the body, the query, or a `January-End-User-ID` header.
3. **Mint a token** with your API key:

   ```bash
   curl https://partners.january.ai/v1.2/auth/client-tokens \
     --header "Authorization: Bearer ${JANUARY_API_KEY}" \
     --header 'Content-Type: application/json' \
     --data '{"end_user_id":"acme-user-8271","scopes":["foods:read","food_logs:read","food_logs:write"],"ttl_seconds":1800}'
   ```

   `scopes` is required. The app's January client uses one token for every call, so list the scopes of every feature your app uses; see the [scope table](/rest-api/authentication.md#client-token-scopes). A call outside the token's scopes fails with `403 scope_insufficient`. `ttl_seconds` is optional: 300–7200, default 1800.
4. **Return January's `201` body unchanged**, with `Cache-Control: no-store`:

   ```json
   {
     "token": "ct-…",
     "expires_in": 1800,
     "expires_at": "2026-09-24T18:35:11.000Z",
     "end_user_id": "acme-user-8271",
     "scopes": ["foods:read", "food_logs:read", "food_logs:write"]
   }
   ```

   The iOS, Android, and Web SDKs read `token` and `expires_in` from this body, and also accept `expiresIn`. The React Native SDK reads `expiresIn`, so its token provider maps the field; see [React Native authentication](/client-sdks/react-native-sdk/getting-started/authentication.md).

{% hint style="warning" %}
Minting fails with `403 forbidden` until **Enable client tokens** is switched on in the [Developer Dashboard → Client tokens](https://dashboard.january.ai/dashboard/client-tokens).
{% endhint %}

Keep the endpoint safe:

* Mint a new token each time the app asks, and don't store tokens on your server. The SDKs cache the token in memory and ask again about 60 seconds before it expires.
* Never log API keys or client tokens, and keep token responses out of analytics and crash reports.
* Serve the endpoint over HTTPS and rate-limit it like the rest of your API.
* To cut a user off, for example after account deletion, revoke their tokens with [`POST /v1.2/auth/client-token-revocations`](/rest-api/authentication.md). Revocation takes effect within 60 seconds.

## Develop with the token relay

Until your endpoint exists, the [January Token Relay](https://github.com/January-ai/january-token-relay) can stand in for it on your computer. Clone it, run `./start.sh`, and paste your API key when asked. Then point your app's token provider at:

* `http://localhost:8787/api/january/client-token` from the iOS Simulator, a browser, or a desktop app.
* `http://10.0.2.2:8787/api/january/client-token` from the Android emulator.

Send a `POST`. A `GET` returns a status page, not a token.

|                           | Token relay (development)                                                   | Your endpoint (production)                                |
| ------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------- |
| Whose token it mints      | The user named in the `January-End-User-ID` header the app sends            | The user your session proves; request headers are ignored |
| How the app authenticates | Nothing on your own computer; a relay token when other devices can reach it | Your normal app session                                   |
| Scopes                    | All ten (set `TOKEN_SCOPES` to narrow them)                                 | Only the ones your app uses                               |
| Response                  | January's body, unchanged                                                   | January's body, unchanged                                 |

The token-provider samples in each SDK guide send both the app session and `January-End-User-ID`, so the same code works against the relay and against your endpoint. Switching over changes only the URL and the session credential. Your production endpoint must ignore the header.

See [Client tokens](/rest-api/authentication.md) for the scope each operation needs and the minting and revocation endpoints.
