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

# Authentication and access

> Choose the right credential, see which routes each one can call, and act on behalf of a person.

Every call to `/api/v1/` carries one API key in the `Authorization` header. The key decides which organization (org) and which environment the request belongs to.

```http theme={null}
Authorization: Bearer <key>
```

The only unauthenticated routes are `GET /health`, `GET /robots.txt` and the inbound event route `POST /api/v1/events/ingest/{source_key}`, which is authenticated per source instead: an HMAC signature, or, for an `ams360_ons` source, a shared authentication code (see [Inbound events](/events/inbound)).

## Two kinds of API key

|                         | Organization key                                                                                                                         | Scoped key                                                                |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Looks like              | `nx_<environment slug>_` then 64 hex characters, for example `nx_dev_...`                                                                | `nxsk_v1_` then a 24-character hex key ID, `_`, and a 43-character secret |
| Who issues it           | You, in the portal: **Settings**, then **API keys**                                                                                      | Nexio, on request                                                         |
| Bound to                | Your org and one environment                                                                                                             | Your org, one environment, a set of engines, and a set of capabilities    |
| What it may call        | Every public route. A key from an environment other than `live` cannot call the families that require a live key or a scoped key (below) | Only routes whose capability it holds, and only engines it is bound to    |
| Revocation takes effect | Within 60 seconds                                                                                                                        | On the next request                                                       |
| Rotation                | Create a new key, move traffic, revoke the old one                                                                                       | Nexio issues a replacement                                                |

The prefix `nxsk_` always means a scoped key. A malformed, unknown or revoked `nxsk_` key is refused with `401` and is never tried as an organization key.

<Note>
  Organization keys are the keys the portal issues. Nexio's error messages sometimes call them "legacy org keys". They are fully supported.
</Note>

### Create an organization key

1. Open **Settings**, then **API keys** in the portal. You need the admin or developer role.
2. Choose **Create API Key**, give the key a name, and pick an environment.
3. Copy the key. The portal shows it once; later requests are verified against a hash of it.

The list shows each active key's name, environment, prefix, creation time and last use. **Rename** changes only the label. **Revoke** stops the key within 60 seconds. Keys created in the portal do not expire.

### Get a scoped key

Ask Nexio for a scoped key when you want least privilege: a key that can, for example, only read records through Records (`records:read`). Say which environment and which capabilities it needs. Nexio records every issue, grant change and revocation in your org's access audit log. There is no portal page or API for scoped keys.

## Capabilities

A capability is one permission a scoped key carries. An organization key carries no capabilities and passes every capability check, except where the tables below say otherwise.

| Capability                  | What it unlocks                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `engines:read`              | `GET /api/v1/engines`, `GET /api/v1/engines/{slug}`, its `config`, `versions` and `versions/{version}`                                                                                                                                                                                                                                                                                                                                                                        |
| `runs:write`                | Submit a run (`POST /api/v1/engines/{slug}/runs`), cancel a run (`POST /api/v1/runs/{run_id}/cancel`), record outcomes and annotations on a run, and the write routes a published engine adds on its own runs                                                                                                                                                                                                                                                                 |
| `runs:read`                 | `GET /api/v1/runs/{run_id}`, the latest-run reads (`GET /api/v1/engines/{slug}/runs/latest` and `POST .../runs/latest/batch`), and the read routes a published engine adds on its own runs                                                                                                                                                                                                                                                                                    |
| `runs:defensibility:read`   | `GET /api/v1/runs/{run_id}/defensibility-packet`                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `runs:test`                 | Not a route. Lets a run request use `test_scenario` ([sandbox fixtures](/reference/sandbox-fixtures))                                                                                                                                                                                                                                                                                                                                                                         |
| `catalog:read`              | Every `GET /api/v1/catalog/...` route except documents: the [served-data](/connections/served-data) reads                                                                                                                                                                                                                                                                                                                                                                     |
| `catalog:documents:read`    | `GET /api/v1/catalog/documents`, the list of source documents the acting seat may open, and `GET /api/v1/catalog/documents/{source_blob_ref}`, the source document behind a citation                                                                                                                                                                                                                                                                                          |
| `webhooks:manage`           | Every `/api/v1/webhooks` route                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `conversations:use`         | `POST /api/v1/converse`, reading conversation instances, their versions, eval scenarios, eval runs and eval run diffs, and every conversation route except export: create, list, read, update, turns, branch, attachments, annotations                                                                                                                                                                                                                                        |
| `conversations:export`      | `GET /api/v1/conversation-instances/{slug}/conversations/{conversation_id}/export`                                                                                                                                                                                                                                                                                                                                                                                            |
| `records:read`              | Every `GET /api/v1/records/...` read except the four ledger reads under `actions:read` and the analyses read under `records:analyze`. Also the opportunity reads under `/api/v1/engines/{engine_id}/opportunities`, and `GET`, `PUT` and `DELETE` on `/api/v1/engines/{engine_id}/opportunities/{opportunity_id}/annotation`. With `runs:write` as well: `POST /api/v1/records/accounts/{client_key}/risk-runs` and `POST /api/v1/records/accounts/{client_key}/program-runs` |
| `records:opportunities:run` | `POST /api/v1/engines/{engine_id}/opportunities/runs`. The path segment is the engine's ID, not its slug                                                                                                                                                                                                                                                                                                                                                                      |
| `actions:write`             | `POST /api/v1/records/actions`, and `POST /api/v1/records/edit-intents/{id}/cancel`, which is read-only and answers `403 overlay_read_only`. With `records:read` as well: `PUT /api/v1/records/accounts/{client_key}/policies/{policy_key}/coverage-record`, which also answers `403 overlay_read_only` ([Writes](/data-services/writes))                                                                                                                                     |
| `actions:read`              | `GET /api/v1/records/actions`, `/workflows`, `/notes`, `/tasks`                                                                                                                                                                                                                                                                                                                                                                                                               |
| `graph:read`                | Every `GET /api/v1/graph/...` route                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `records:analyze`           | `/api/v1/records/analyses` routes. Built, not yet enabled: they also require a per-org switch that Nexio turns on                                                                                                                                                                                                                                                                                                                                                             |

The three `records:*` names share the `/api/v1/records` route prefix. `records:read` gates the Records reads, including the generic [family read](/data-services/families); the action ledger reads take `actions:read` and the analyses reads take `records:analyze`. The earlier prefix `/api/v1/book`, the `book:` capabilities and the `X-Nexio-Book-*` headers keep working as aliases.

A scoped key without the capability a route needs gets `403 insufficient_capability`.

Five more names exist only inside the portal and can never be put on a key: `engines:manage`, `instances:manage`, `environments:manage`, `connections:manage` and `runs:trace`. The public routes that need `engines:manage`, `instances:manage` or `environments:manage` accept organization keys only (below). `connections:manage` and `runs:trace` gate portal pages only. In the portal, these names follow from a person's role permissions: for example, a role with the `engines:manage` permission may author both engines and conversation instances (`instances:manage`). See [Team and roles](/platform/team-and-roles).

### Engine binding

A scoped key lists the engines it may use. In the [API reference](/api-reference/introduction), every route that accepts a scoped key and has an engine slug or a run ID in its path checks that the engine is in the list; if it is not, the answer is `403 engine_binding_forbidden`. A scoped key bound to no engines cannot call those routes. `GET /api/v1/engines` returns only the engines the key is bound to.

## Routes that accept only organization keys

These routes refuse every scoped key with `403 insufficient_capability`, whatever capabilities it holds. Use an organization key, or do the work in the portal.

| Area                            | Routes                                                                                                                              |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Environments                    | `POST`, `GET /api/v1/environments`, `PATCH`, `DELETE /api/v1/environments/{slug}`                                                   |
| Engine management               | `POST /api/v1/engines`, `PATCH /api/v1/engines/{slug}`, `PUT .../config`, `POST .../config/validate`, `POST .../versions` (publish) |
| Conversation instance authoring | Create, update and publish an instance, create, update and retire eval scenarios, start an eval run, promote an annotation          |

Routes that serve Nexio-hosted applications refuse scoped keys the same way. One of them is the app data store ([Application records](/data-services/app-records)), which also refuses organization keys from environments other than `live`.

## Routes that need a live key or a scoped key

On these families an organization key is accepted only if it was created for the `live` environment. An organization key from any other environment gets:

```json theme={null}
{
  "code": "scoped_key_required",
  "message": "This route requires a scoped API key with an explicit capability grant; legacy org keys are not accepted here"
}
```

with status `403`. A scoped key from any environment passes this check and then needs the capability.

| Family                                                                 | Capability a scoped key needs                                                                                                                    |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/api/v1/records/...` (Records reads, actions, run launches, analyses) | `records:read`, `actions:read`, `actions:write`, `records:analyze`, and `runs:write` beside `records:read` for the risk and program run launches |
| `/api/v1/engines/{engine_id}/opportunities...`                         | `records:read`, `records:opportunities:run`                                                                                                      |
| `/api/v1/graph/...`                                                    | `graph:read`                                                                                                                                     |
| `/api/v1/catalog/documents/...`                                        | `catalog:documents:read`                                                                                                                         |

## Acting for a person

The key proves the org. When your server acts for a specific person in that org, send that person's stable user ID from your identity provider in `X-Nexio-Acting-Principal`, and on Records routes their verified sign-in email in `X-Nexio-Acting-Email`. Nexio does not issue user IDs to your end users. Always send the stable ID, never the email, in `X-Nexio-Acting-Principal`.

```http theme={null}
X-Nexio-Acting-Principal: 00u8f2k4d1XyZ7hQ9357
X-Nexio-Acting-Email: dana.ortiz@harborgroup.example
```

* The header can only narrow what the key already allows. What the person may reach comes from your own systems; see [Access plane](/data-services/access).
* On Records reads, the person's own authority decides which records come back. See [Authority and scope](/data-services/scope) for the related identity headers and the signed assertion.
* On a run, the acting principal is recorded as who submitted it and is part of the idempotency scope. A run of a `matching` engine without it answers `400 run_requires_acting_principal`. A body `submitted_by` is accepted only alongside the header and equal to it; without the header, or with a different value, the answer is `400 acting_principal_mismatch`.

Send these headers only from your server. The API sends no CORS headers, so browsers cannot call it directly.

## Errors

| HTTP | Code                        | Cause                                                                                       | Fix                                                                                                         |
| ---- | --------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| 401  | `unauthorized`              | Missing header, not a `Bearer` scheme, empty token, or an unknown, malformed or revoked key | Send a current key as `Authorization: Bearer <key>`                                                         |
| 403  | `insufficient_capability`   | The scoped key lacks the route's capability, or the route accepts organization keys only    | Ask Nexio to add the capability, or use an organization key                                                 |
| 403  | `scoped_key_required`       | An organization key from a non-live environment called a live-only family                   | Use the live organization key, or a scoped key                                                              |
| 403  | `engine_binding_forbidden`  | The scoped key is not bound to this engine or to the run's engine                           | Ask Nexio to bind the engine to the key                                                                     |
| 403  | `identity_unmapped`         | A Records route under the shadow or lit access posture got no acting person it could map    | Send `X-Nexio-Acting-Principal` and `X-Nexio-Acting-Email`. See [Authority and scope](/data-services/scope) |
| 400  | `acting_principal_mismatch` | Body `submitted_by` is set and `X-Nexio-Acting-Principal` is missing or differs             | Send the header, and omit `submitted_by` or send the same value                                             |
| 503  | `auth_unavailable`          | Nexio could not reach its key store                                                         | Retry with backoff                                                                                          |

Authentication runs before rate limiting, so a bad key gets `401`, never `429`. See the [error reference](/reference/errors) for every code.

## Next

<CardGroup cols={2}>
  <Card title="Environments" icon="layer-group" href="/environments">
    Keep sandbox traffic apart from live traffic.
  </Card>

  <Card title="Requests and errors" icon="circle-exclamation" href="/reference/requests-and-errors">
    Headers, status codes and retries.
  </Card>
</CardGroup>
