Citera home
Browse documentation

API keys

API keys

Create, list, update, rotate, and revoke keys.

Summarize with AIChatGPTClaudePerplexity

Not brand-scoped. The principal is a key with keys:manage or a session token. Everything runs as the authenticated owner; owner IDs in bodies do not exist.

POST /v1/api-keys — create a key

Requires the flag api.key_issuance.enabled.

Body field

Type

Rules

name

string

1–120 chars

allowed_surfaces

("api"|"mcp")[]

1–2; each must be in the owner's enrollment

scopes

scope[]

≥ 1; requirements must be satisfied; beta scopes must be enrolled; publishing:write only while publishing is enabled

brand_ids

uuid[]

1–100; all owned by the caller (422, param: "brand_ids")

expires_at

RFC 3339 | null

optional; must be in the future

Fails with 429 quota_exceeded (non-retryable) when the account already holds max_keys active keys. Response 201: an api_key plus token (shown once).

curl -s "${CITERA_API_BASE_URL%/}/v1/api-keys" -X POST \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"CI reader","allowed_surfaces":["api"],"scopes":["brands:read","content:read"],"brand_ids":["11111111-1111-4111-8111-111111111111"]}'
curl -s "${CITERA_API_BASE_URL%/}/v1/api-keys" -X POST \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"CI reader","allowed_surfaces":["api"],"scopes":["brands:read","content:read"],"brand_ids":["11111111-1111-4111-8111-111111111111"]}'
curl -s "${CITERA_API_BASE_URL%/}/v1/api-keys" -X POST \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"CI reader","allowed_surfaces":["api"],"scopes":["brands:read","content:read"],"brand_ids":["11111111-1111-4111-8111-111111111111"]}'
curl -s "${CITERA_API_BASE_URL%/}/v1/api-keys" -X POST \
  -H "Authorization: Bearer $ADMIN_KEY" -H "Content-Type: application/json" \
  -d '{"name":"CI reader","allowed_surfaces":["api"],"scopes":["brands:read","content:read"],"brand_ids":["11111111-1111-4111-8111-111111111111"]}'

GET /v1/api-keys — list keys

Query: limit, cursor, status (active|revoked|expired|disabled). Metadata only (no tokens or hashes).

GET /v1/api-keys/{key_id} — get a key

PATCH /v1/api-keys/{key_id} — update a key

Body (at least one field): name, allowed_surfaces, scopes, brand_ids (replaces bindings), expires_at (null clears it). Only active keys (409 invalid_state). The complete resulting policy is re-validated like a create. The local key cache is invalidated; other replicas pick up the change within 30 s.

POST /v1/api-keys/{key_id}/rotate — rotate

Body: { "revoke_previous": true } (default true; send at least {} with Content-Type: application/json). Creates a new key with the same name, scopes, surfaces, brands and expiry (rotated_from_key_id set) and returns it with its token (201). With revoke_previous: false the old key stays active and the max-keys limit applies. Requires api.key_issuance.enabled.

POST /v1/api-keys/{key_id}/revoke — revoke

Idempotent and irreversible. No body. Returns the key with status: "revoked".