---
title: End-User authentication
description: The impersonation key and the user token that every End-User API request sends, and what the API refuses
---

Every End-User API request sends two credentials. An API key in the `X-API-Key` header identifies
your application. A JWT in the `Authorization` header identifies the user. The request has the
permissions of that user, and nothing more.

| Credential        | Header                  | What it does                                                                         |
| ----------------- | ----------------------- | ------------------------------------------------------------------------------------ |
| Impersonation key | `X-API-Key`             | Identifies your application. The key must have the access type **Impersonate user**. |
| User token        | `Authorization: Bearer` | Identifies the user. The email login flow issues it.                                 |

The base URL of the End-User API is `{BASE_URL}/api/v2/end-user`.

## Get an impersonation key

Create the key in the Seamless OS portal, under **API tokens**. Select the access type
**Impersonate user**. A key with the access type **Full API access** gets a `403` response from
every End-User operation.

An impersonation key alone reaches no data. Without a user token, the API answers `401`. Thus
the key identifies your application, and the user token is the credential that you must protect.
Store the user token like a session, and send every request over HTTPS.

## Get a user token

The email login flow has two steps. Both operations are public: they take no API key and no
user token. Both are rate limited, and both answer `429 Too Many Requests` at the limit.

Start the login. The API sends a 6-digit code to the email address of the user:

```bash
curl -X POST "{BASE_URL}/api/v2/end-user/auth/email/start" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "emma.johnson@example.com"
  }'
```

The API answers `202 Accepted` also for an unknown email address, so a caller cannot find out
which addresses exist. The response carries a `nonce` that names this login attempt:
```json
{
  "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "expiresIn": 300,
  "createdAt": "2026-07-12T10:00:00Z",
  "expiresAt": "2026-07-12T10:05:00Z"
}
```

Then send the code together with the email address and the nonce:

```bash
curl -X POST "{BASE_URL}/api/v2/end-user/auth/email/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "emma.johnson@example.com",
    "nonce": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "code": "482913"
  }'
```

On success the API answers with the token:
```json
{
  "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "tokenType": "Bearer",
  "expiresIn": 604800,
  "userId": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
```

Send `accessToken` as a Bearer token on every later request. The token expires after
`expiresIn` seconds. When it expires, start a new login.

## Send both credentials

```bash
curl "{BASE_URL}/api/v2/end-user/accounts/me" \
  -H "X-API-Key: $API_KEY" \
  -H "Authorization: Bearer $USER_TOKEN"
```

The response is the profile of the user that owns the token, with the customers that the user
belongs to.

## What the API refuses

| Request                                                  | Response           | `internalCode` |
| -------------------------------------------------------- | ------------------ | -------------- |
| A key with full API access, with or without a user token | `403 Forbidden`    | `4337`         |
| An impersonation key with no user token                  | `401 Unauthorized` | `4338`         |
| A user token with no key, or with an unknown key         | `401 Unauthorized` |                |

The two login operations are the exception. They answer without a key and without a token.

## The Platform API and a user token

The [Platform API](/api-reference/authentication.md) also accepts a user token beside a full-access
key on a set of user-context operations. That set gets smaller as applications move to the
End-User API, and a new application must not depend on it.
