# Authentication

> API tokens, access levels, allowed IP addresses.

## API tokens

Every request carries a workspace API token in the `Authorization` header:

```http
Authorization: Bearer upm_...
```

- A token belongs to one workspace. Everything you read or change is in that workspace.
- Tokens are created under **Integrations**, **API**, **Generate new token** by users with the Administrator or Manager role. The value is shown once; Echo stores only a hash of it.
- The token list in the app shows each token's first characters, its access, its allowed IP addresses and when it was last used.
- A workspace can have at most 50 active tokens.
- Use the token only in your own backend. The API sends no CORS headers, so it can't be called from a browser anyway.

The `GET /openapi.yaml` document is the only endpoint that needs no token.

## Access levels

A token has one of two access levels, chosen when it is created (it can't be changed later):

| Access | What it allows |
|---|---|
| Read only | Every `GET` endpoint the creator's role allows. |
| Full access | Reads and writes, as far as the creator's role allows. |

A token always acts with its **creator's current role**: the access level can only narrow it. Each endpoint needs a right in one area:

| Endpoints | Read needs | Write needs |
|---|---|---|
| Subscribers, groups, segments, fields, imports | Subscribers: view | Subscribers: manage |
| Campaigns | Campaigns: view | Campaigns: manage |
| Automations | Automations: view | Automations: manage |
| Webhooks | Settings: view | Settings: manage |
| `GET /timezones`, `POST /batch` | any valid token | any valid token |

The built-in Administrator and Manager roles have every right above. A request without the right answers `403 forbidden`:

```json
{"error":{"code":"forbidden","message":"You don't have permission to do this."}}
```

Some answers depend on the rights too:

- Webhook URLs are shown in full only to a token with Settings: manage. Others see the origin only, for example `"url":"https://hooks.example.com/…"`.
- In campaign activity and workflow runs, the subscriber's `email` and the `search` parameter need Subscribers: view. Without it, the rows carry the subscriber's `id` only and `search` answers `403`.

## Allowed IP addresses

A token can be limited to up to 20 IP addresses or ranges (for example `203.0.113.7` or `203.0.113.0/24`). A request from any other address answers:

```http
HTTP/1.1 403 Forbidden
Content-Type: application/json

{"error":{"code":"ip_not_allowed","message":"This API token can't be used from your IP address."}}
```

## When a token stops working

Echo checks the token on every request, so these take effect at once:

| Change | Answer |
|---|---|
| The token is revoked | `401 unauthenticated` |
| The creator is removed from the workspace, or their user is disabled | `401 unauthenticated` (the app marks the token **Inactive**) |
| The creator's role changes | The new role applies from the next request |
| The workspace is suspended | `403 workspace_suspended` |
| The workspace requires two-factor authentication and the creator hasn't set it up | `403 twofa_enforced` (the token works again once they set it up) |

Changes made with a token are recorded in the workspace's activity log under the token's name.

## Authentication errors

A missing, malformed, unknown or revoked token:

```bash
curl -i "$ECHO_API/subscribers"
```

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Www-Authenticate: Bearer realm="upmail"

{"error":{"code":"unauthenticated","message":"Missing, unknown or revoked API token."}}
```

Failed authentication doesn't count against the rate limit, and its answer has no `X-RateLimit-*` headers.
