
# API Keys

A session cookie suits a browser and nothing else. When a script, a CI job or a service has to call your instance, it needs a credential it can hold — and **self-service API keys** are that credential: a signed-in user mints their own key, presents it in the `x-api-key` header, and revokes it when the job is done.

Keys are off by default. One boolean turns them on.

```yaml
auth:
  strategies:
    - type: emailAndPassword
  apiKeys: true
```

| Property  | Description                                                                                                                                              |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKeys` | Boolean. `true` mounts the `/api/auth/api-key/*` endpoints and makes `x-api-key` a valid credential on every auth-gated route. Omitted (default) is off. |

When the option is absent the surface does not acknowledge its own existence: `/api/auth/api-key/*` answers **404**, and an `x-api-key` header is simply ignored.

## Authenticating with a key

Present the key in the **`x-api-key`** header. It works on every auth-gated `/api/*` route, from any client — no cookie, no sign-in round-trip.

```bash
curl -H "x-api-key: $SOVRIUM_API_KEY" \
  http://localhost:3000/api/tables/notes/records
```

:::callout
**`Authorization: Bearer` authenticates nothing.** Sovrium accepts exactly two credential forms on `/api/*`: the session cookie, and `x-api-key`. A valid key presented as a Bearer token resolves no session and returns `401` — a deliberate contract, so a long-lived credential travels on exactly one audited path rather than two. If a request that should work returns `401`, check the header name first.
:::

## Minting, listing and revoking

Four endpoints, mounted under `/api/auth/api-key/`. Each is scoped to the calling session: a user sees and manages **their own** keys and nobody else's.

| Method | Path                       | Body / query              | Purpose                                                        |
| ------ | -------------------------- | ------------------------- | -------------------------------------------------------------- |
| `POST` | `/api/auth/api-key/create` | `{ "name": "CI deploy" }` | Mint a key. The response carries the plaintext value **once**. |
| `GET`  | `/api/auth/api-key/list`   | —                         | List the caller's keys (metadata only).                        |
| `GET`  | `/api/auth/api-key/get`    | `?id=<keyId>`             | Read one of the caller's keys back by id.                      |
| `POST` | `/api/auth/api-key/delete` | `{ "keyId": "<keyId>" }`  | Revoke a key.                                                  |

Mint one from an authenticated browser session:

```bash
curl -X POST http://localhost:3000/api/auth/api-key/create \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d '{ "name": "Nightly backup job" }'
```

```json
{
  "id": "aK9tZ...",
  "name": "Nightly backup job",
  "key": "sk_live_9f2c...",
  "createdAt": "2026-08-26T09:14:22.000Z"
}
```

`key` is the only thing you have to keep. Store it wherever the job reads its secrets from.

:::callout
**Shown once, then never again.** Only the create response carries the plaintext value. `list` and `get` describe a key — id, name, timestamps — but never re-issue it, under any field name, because the server does not have it: the stored column holds a SHA-256 digest, not the credential. Someone who can read your database still cannot authenticate with what they find there. Lose the value and the remedy is to revoke and mint a new one.
:::

Revocation is a **deletion**, not a flag: the row is removed, and the identical request that succeeded a moment earlier answers `401`.

Give each key the name of the job that will carry it. Names are how you tell, six months later, which key belongs to the CI pipeline and which to the reporting script you decommissioned.

## What a key is allowed to do

A key carries the role of the user who minted it — **resolved live, on every request**, not frozen at creation.

- A `member`'s key can do exactly what that member can do. It can never do more.
- Promote or demote the owner and their existing keys follow immediately. Demoting a user narrows every key they hold, without invalidating any of them.
- A caller cannot widen their own key. Supplying a permissions payload to `create` does not produce an escalated key — the grant is a function of who asked, never of what they asked for.

This is why a key needs no permission configuration of its own: it is a second way to present an identity you already have, not a new identity.

### Banning suspends a key; unbanning restores it

Banning a user stops their keys authenticating, immediately and on every route. The key row survives untouched, so lifting the ban restores the same keys — a ban suspends a credential rather than destroying it, which matters because Better Auth clears a temporary ban on its own once it expires.

Sovrium sets no expiry, so a key stays valid until it is revoked or its owner is banned. Treat one as you would a password: scope it to a job, store it in a secret manager, and revoke it when the job ends.

## The console page

Signed-in operators manage their own keys at **`/_admin/api-keys`** — mint, copy once, revoke — beside the other pages about their own account in the [admin dashboard](/en/docs/admin-dashboard).

The console sits behind the usual admin-only `/_admin/*` gate, but the API does not: **any signed-in user can mint and use keys** through `/api/auth/api-key/*`. A `member` who never sees the dashboard can still hold a key and authenticate a script with it.

## Not the same thing as a Connection

Two features in Sovrium involve something called an API key, and they point in opposite directions.

|                      | **API keys** (this page)                 | **[Connections](/en/docs/automation-connections)** |
| -------------------- | ---------------------------------------- | -------------------------------------------------- |
| Direction            | **Inbound** — credentials Sovrium issues | **Outbound** — credentials Sovrium presents        |
| Who is authenticated | A caller, to your instance               | Your instance, to a third-party service            |
| Where it is declared | `auth.apiKeys`                           | `app.connections[]`                                |
| Where it is managed  | `/_admin/api-keys`                       | `/_admin/connections`                              |

A Connection of `type: apiKey` holds **somebody else's** key so an automation can call their API. This page is about the keys your own instance issues so somebody can call yours. Turning one on says nothing about the other.

## Related Pages

- [Sessions](/en/docs/auth-sessions) — the cookie-based credential keys sit alongside.
- [REST API Overview](/en/docs/api-reference) — the authentication contract that governs every endpoint.
- [Roles & RBAC](/en/docs/auth-roles-rbac) — the roles a key inherits.
- [User Management](/en/docs/user-management) — banning, unbanning and role changes.
- [Connections](/en/docs/automation-connections) — outbound credentials, the other direction.
- [Security Hardening](/en/docs/security-hardening) — deploying a credentialed surface safely.
