API keys
A Catentio API key is a long-lived bearer credential that lets a server-side caller authenticate to the catentio runtime (not the control plane — see the callout below) as a specific workspace. Keys are minted in the portal, scoped to one customer_id, and presented on requests as:
Authorization: Bearer cat_pk_5fGZ8h2KqWnpL3vBxYsR6tAjMdC1eIuN
Use a key when:
- The caller isn't you — it's your backend, a cron job, an external integration.
- You want a credential whose lifetime you control (rotation, revocation) independent of any Huudis session.
- You want zero round-trips to Huudis on every call.
If the caller is you, use the OIDC device flow instead — an API key isn't tied to a Huudis identity and bypasses MFA.
The control plane doesn't check API keys — the runtime does. The
/v1/api-keysendpoints on this page (list/create/revoke) are portal-session routes: they mint and manage thecat_pk_*secret, but the secret itself is verified by the catentio runtime's auth guard on requests you send it directly, not by anything on the control plane. There is only one real prefix,cat_pk_*; don't rely on acat_ak_*prefix appearing anywhere — it isn't part of the shipped auth model.
The API key object
{
"id": 42,
"customer_id": "internal",
"name": "production-cron",
"key_prefix": "cat_pk_5fGZ8h2K",
"created_at": "2026-05-01T08:00:00.000Z",
"last_used_at": "2026-05-12T10:42:00.123Z",
"revoked_at": null
}
| Field | Type | Notes |
|---|---|---|
id |
int | Auto-incrementing integer; the PK used in delete URLs. |
customer_id |
string | Owning workspace. Force-set server-side from the session. |
name |
string | Human-readable label. Shown in the portal listing. |
key_prefix |
string | First 12 characters of the secret, for at-a-glance identification. |
created_at |
timestamp | ISO 8601 UTC. |
last_used_at |
timestamp | null | Updated on every request; rounded to the minute. |
revoked_at |
timestamp | null | When deleted; soft-deleted rows stay queryable for audit. |
The secret (cat_pk_*) is only returned once, on the create response. After that the API only ever shows key_prefix — the full secret lives in your secret manager, never on the control plane in plaintext.
scopesis a placeholder. The create body accepts ascopesfield but the control plane strips it before forwarding (payload.pop("scopes", None)) — v1 keys always carry an emptyscopesobject, which means full access to the customer's data. There's no scope value worth setting yet; per-resource scoping is a later release, same as the object table below already says.
Endpoints
All endpoints scope to the caller's customer_id automatically. There's no way to list, mint, or revoke another workspace's keys even with a forged URL.
List API keys
GET /v1/api-keys
Returns every key for the caller's workspace — revoked ones included, sorted with active keys first (then newest-first). Check revoked_at on each row to tell them apart; there's no ?status= filter to ask for active-only.
const { data } = await catentio.apiKeys.list();
keys = catentio.apiKeys.list()
curl 'https://catent.io/api/v1/cp/v1/api-keys' \
-H "Cookie: catentio_session=<session cookie set at login>"
Create an API key
POST /v1/api-keys
Mint a new PAT. Body:
{ "name": "production-cron" }
The customer_id is force-set from the verified session; you can't pass it. Likewise scopes is stripped in v1 — the only available scope is full workspace access. Per-resource scopes land in a later release.
Response (the only time the secret is shown):
{
"id": 42,
"customer_id": "internal",
"name": "production-cron",
"key": "cat_pk_5fGZ8h2KqWnpL3vBxYsR6tAjMdC1eIuN",
"key_prefix": "cat_pk_5fGZ8h2K",
"created_at": "2026-05-01T08:00:00.000Z"
}
const { key } = await catentio.apiKeys.create({ name: 'production-cron' });
console.log('Store this NOW:', key);
result = catentio.apiKeys.create({"name": "production-cron"})
print("Store this NOW:", result["key"])
curl -X POST 'https://catent.io/api/v1/cp/v1/api-keys' \
-H "Cookie: catentio_session=<session cookie set at login>" \
-H "Content-Type: application/json" \
-d '{"name":"production-cron"}'
The secret is shown once. Copy it into your secret manager immediately. The control plane stores only a sha256 hash; there is no recovery flow. If you lose the secret you must mint a new key and revoke the old one.
Revoke an API key
DELETE /v1/api-keys/{id}
Soft-deletes a key (sets revoked_at; the row stays for audit). The CP verifies the key belongs to the caller's customer_id before forwarding — cross-workspace deletes return 403 not_your_key.
Returns 204 No Content. After revocation, any request signed with the key gets a plain-text 401 from the runtime ("api key revoked").
await catentio.apiKeys.delete(42);
catentio.apiKeys.delete(42)
curl -X DELETE 'https://catent.io/api/v1/cp/v1/api-keys/42' \
-H "Cookie: catentio_session=<session cookie set at login>"
Rotation
Keys don't expire on their own — the v1 control plane has no max-age policy. Rotate them yourself:
- Mint a new key with the next name (e.g.,
production-cron-2026q3). - Deploy it: update your secret manager + restart any callers that cache the key.
- Smoke test with the new key.
- Revoke the old key via
DELETE /v1/api-keys/{id}.
Rotate when:
- A team member with access leaves.
- A key was committed to source control or otherwise exposed.
- On a calendar schedule — we recommend quarterly.
Errors
| Status | Detail | When |
|---|---|---|
| 400 | name required / name too long (max 120 chars) |
Bad create body. |
| 401 | invalid api key |
The Authorization: Bearer cat_pk_... secret (used against the runtime, not this CP resource) doesn't match any stored hash. |
| 401 | api key revoked |
The secret matches a key whose revoked_at is set. |
| 403 | not_your_key |
Tried to revoke a key belonging to another workspace. |
| 403 | plan_limit_exceeded |
Workspace hit its api_keys tier cap on create. |
| 502 | — | CP couldn't reach the runtime. |
These are plain-text detail strings from the runtime's auth guard, not machine code values — there's no structured error-code taxonomy on this resource beyond not_your_key, which the CP itself raises.
Events
API-key CRUD isn't currently emitted as outbox events — reserved for a later release.
Security model
- The secret is hashed (sha256) at rest; the row stores the hash plus a display-only
key_prefix. The plaintext is never logged. last_used_atis updated asynchronously; treat it as advisory, not authoritative for auditing.- Every key is scoped to the
customer_idit was minted under — there's no cross-workspace surface for a leaked key to reach, independent of any user-count question. - No IP allowlisting in v1; if you need that, terminate the key at your own proxy and add it there.
Next
- Authentication — the three credential shapes, end-to-end.
- Portal → API keys — the dashboard equivalents.