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.
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.
curl -H "x-api-key: $SOVRIUM_API_KEY" \
http://localhost:3000/api/tables/notes/recordsAuthorization: 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:
curl -X POST http://localhost:3000/api/auth/api-key/create \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{ "name": "Nightly backup job" }'{
"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.
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
createdoes 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.
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 | |
|---|---|---|
| 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 — the cookie-based credential keys sit alongside.
- REST API Overview — the authentication contract that governs every endpoint.
- Roles & RBAC — the roles a key inherits.
- User Management — banning, unbanning and role changes.
- Connections — outbound credentials, the other direction.
- Security Hardening — deploying a credentialed surface safely.
Last updated August 28, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.