# Authentication

This reference page specifies how requests are authenticated: the header, key formats, how scopes are checked and every authentication and authorisation error. For a task-oriented introduction, see [Authentication](/getting-started/authentication) in Getting started.

## Header format

```http
Authorization: Bearer rai_test_...
Authorization: Bearer rai_live_...
```

- The scheme is `Bearer`, followed by one space and the key. No quotes, no trailing characters.
- A key is `rai_test_` or `rai_live_` followed by 40 letters and digits. The prefix decides the mode.
- Send the header on every request. There are no sessions, cookies or query-string keys.
- The only operation that takes no key is [`GET /recordings/{recording_token}`](/api/recordings#download-recording-audio): its URL carries a short-lived token instead, and an `Authorization` header sent to it is ignored.

Only RelationsAI API keys are accepted. Console sign-in tokens and any other credentials answer [`invalid_api_key`](/errors/invalid_api_key); older `sk_live_` keys answer [`legacy_api_key_not_supported`](/errors/legacy_api_key_not_supported).

## What a key is bound to

| Binding | Effect |
| --- | --- |
| One workspace | Every request acts in that workspace. Another workspace's ids answer [`resource_not_found`](/errors/resource_not_found). |
| One mode | `rai_test_` keys see test-mode data only, `rai_live_` keys live data only. The other mode's ids answer [`resource_not_found`](/errors/resource_not_found). |
| Its scopes | Chosen when the key is created; see [API keys](/getting-started/api-keys#scopes). |
| Its creator | The key never does more than the member who created it may do now. |
| An expiry | Live keys always expire; test keys may not. |

## Scope checks

Each operation requires one scope, listed in the API reference (as `x-required-scopes` in the OpenAPI document). Every key also has `workspace:read`.

A request is allowed when the operation's scope is among the key's **effective scopes**: the scopes the key was created with, limited to what its creator's current role allows. Effective scopes are worked out on every request, and [`GET /workspace`](/api/workspace#retrieve-the-workspace) returns them as `api_key.scopes`.

- The key was never given the scope: [`insufficient_scope`](/errors/insufficient_scope).
- The key has the scope but its creator's role no longer allows it, or the creator left the workspace: [`insufficient_permissions`](/errors/insufficient_permissions).

Both are `403` and carry `details.required_scopes`:

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not have the scope this operation requires.",
    "details": { "required_scopes": ["calls:write"] },
    "request_id": "req_4k7m2qat5xvz3c6vqnwpbdfh",
    "retry": "no",
    "doc_url": "https://docs.relationsai.com/errors/insufficient_scope"
  }
}
```

Some problems are reported before the key is checked: an unknown path or method (`404`, `405`) and a body that is not JSON, is too large or has the wrong content type (`400`, `413`, `415`). The fields of the body are validated only after authentication and permission checks, so a key without the scope gets `403` even for a body with invalid fields.

## Workspace and network checks

After the key itself, each request is checked against the workspace:

- **API access.** During the private preview the workspace must be enabled for the API, else [`api_access_not_enabled`](/errors/api_access_not_enabled).
- **IP allowlist.** If the workspace has an IP allowlist in its security settings, requests from other addresses answer [`ip_not_allowed`](/errors/ip_not_allowed).
- **Browsers.** A request with a live key and an `Origin` header (which browsers send) answers [`live_key_not_allowed_in_browser`](/errors/live_key_not_allowed_in_browser). This is decided before the key is looked up, so browser code cannot even learn whether a live key is valid. Test keys may be used from browser-based tools; browsers can only read responses from origins the API allows.

## Fails closed

If the API cannot complete one of these checks (for example, it cannot look up the key or read the workspace's IP allowlist), it refuses the request with [`service_unavailable`](/errors/service_unavailable) and a `Retry-After` header. It never lets a request through because a check could not run. Nothing was done; retry later with the same `Idempotency-Key`.

## Errors

- [`authentication_required`](/errors/authentication_required) (401): No `Authorization` header, a scheme other than `Bearer`, or an empty token.
- [`invalid_api_key`](/errors/invalid_api_key) (401): Not a valid RelationsAI API key.
- [`api_key_expired`](/errors/api_key_expired) (401): The key expired, or its rotation grace period ended.
- [`api_key_revoked`](/errors/api_key_revoked) (401): The key was revoked.
- [`legacy_api_key_not_supported`](/errors/legacy_api_key_not_supported) (401): An `sk_live_` key.
- [`insufficient_scope`](/errors/insufficient_scope) (403): The key lacks the operation's scope.
- [`insufficient_permissions`](/errors/insufficient_permissions) (403): The key's creator no longer holds the permission, or left the workspace.
- [`api_access_not_enabled`](/errors/api_access_not_enabled) (403): The workspace is not enabled for the API.
- [`ip_not_allowed`](/errors/ip_not_allowed) (403): The request's address is not on the workspace IP allowlist.
- [`live_key_not_allowed_in_browser`](/errors/live_key_not_allowed_in_browser) (403): A live key sent from a browser.
- [`service_unavailable`](/errors/service_unavailable) (503): A check could not run; retry after `Retry-After`.

Every one of these means the request did nothing: nothing was created, dialled or charged, and nothing was stored under an `Idempotency-Key` you sent, so you can send the same key again once the cause is fixed. All `401` and `403` codes have `retry: "no"`: retrying unchanged fails the same way.
