API Keys
Machine credentials scoped to a realm. Used for server-to-server authentication instead of a user access token. All endpoints are under /v1/realms/{slug}/keys.
Every endpoint requires ROLE_REGISTERED — only registered users can manage keys, and a user can only see their own keys.
Create
POST /v1/realms/{slug}/keys
Create a new API key in the given realm. The plaintext secret is returned once — the server never exposes it again. Store it immediately.
- Auth:
ROLE_REGISTERED - Rate limit: 10 / 60s
Path params: slug — realm slug.
Request
{
"name": "Production backend",
"scopes": [ "stumper:read", "stumper:write" ],
"origins": [ "mywebsite.com", "*.mywebsite.com" ],
"expires_at": "2027-01-01T00:00:00+00:00"
}
| Field | Type | Constraints |
|---|---|---|
name | string | required, not blank, max 128 chars. |
scopes | array<string> | default []. |
origins | array<string> | default []. Allow-list of HTTP origins (host, optionally with *. wildcard) the key may be presented from. Empty means unrestricted — required for browser-embedded keys like DDB tooltips. |
expires_at | string|null | ISO 8601, must be in the future. |
Response — 201 Created
{
"key": {
"id": 17,
"name": "Production backend",
"prefix": "kat_live_abc123",
"scopes": [ "stumper:read", "stumper:write" ],
"origins": [ "mywebsite.com", "*.mywebsite.com" ],
"is_active": true,
"last_used_at": null,
"expires_at": "2027-01-01T00:00:00+00:00",
"created_at": "2026-04-18T12:00:00+00:00",
"revoked_at": null
},
"plaintext": "kat_live_abc123_<long-random-string>"
}
Errors
| Code | Meaning |
|---|---|
400 | expires_at is in the past. |
404 | Realm not found. |
List
GET /v1/realms/{slug}/keys
List the caller's API keys for the given realm.
- Auth:
ROLE_REGISTERED
Response — 200 OK
{
"keys": [
{
"id": 17,
"name": "Production backend",
"prefix": "kat_live_abc123",
"scopes": [ "stumper:read" ],
"is_active": true,
"last_used_at": "2026-04-18T11:55:00+00:00",
"expires_at": null,
"created_at": "2026-04-01T00:00:00+00:00",
"revoked_at": null
}
]
}
Show
GET /v1/realms/{slug}/keys/{id}
Get a single key by ID. Only returns keys owned by the caller.
- Auth:
ROLE_REGISTERED
Response — 200 OK
{
"key": { "…": "as above" }
}
Errors
| Code | Meaning |
|---|---|
404 | Realm or key not found. |
Update
PATCH /v1/realms/{slug}/keys/{id}
Update a key's name, scopes, or active flag. Cannot change the secret; use /roll for that.
- Auth:
ROLE_REGISTERED
Request
{
"name": "Production backend (EU)",
"scopes": [ "stumper:read" ],
"origins": [ "mywebsite.com" ],
"is_active": true
}
All fields optional. name is constrained to max 128 chars. Setting origins replaces the existing allow-list (pass [] to remove all restrictions).
Response — 200 OK
{
"key": { "…": "updated key" }
}
Errors
| Code | Meaning |
|---|---|
404 | Realm or key not found. |
Revoke
DELETE /v1/realms/{slug}/keys/{id}
Revoke an API key. Soft-deletes; the key can no longer authenticate.
- Auth:
ROLE_REGISTERED
Response — 200 OK
{
"revoked": true
}
Errors
| Code | Meaning |
|---|---|
404 | Realm or key not found. |
Roll secret
POST /v1/realms/{slug}/keys/{id}/roll
Regenerate a key's secret. The previous plaintext stops working immediately. The new plaintext is returned once.
- Auth:
ROLE_REGISTERED - Rate limit: 5 / 60s
Response — 200 OK
{
"key": { "…": "updated key" },
"plaintext": "kat_live_abc123_<new-long-random>"
}
Errors
| Code | Meaning |
|---|---|
404 | Realm or key not found. |