Section 11

11. Keys, scopes and limits

Scopes

ScopeGrants
inference:callevery inference endpoint
catalog:readlist and inspect models
catalog:writeregister and deregister your own model aliases (never rents compute)
usage:readusage rollups and analytics
billing:readbalance, credits, costs, entitlement
keys:read / keys:writemanage keys
instances:read / instances:writemanage compute
webhooks:read / webhooks:writemanage webhooks
audit:readread your audit log
identity:read / identity:writetenancy structure
training:read / training:writereserved

A key can only ever be granted scopes the minting principal already holds, and PATCH /v1/keys/{id} can only reduce them.

Per-key controls

At mint time: rate_limit_rpm, expires_at. By PATCH: name, scopes (reduction only — widening needs a new key), rate_limit_rpm, expires_at, and spend_cap:

bash
curl -X PATCH "$BASE/v1/keys/$KEY_ID" \
  -H "Authorization: Bearer $SESSION_JWT" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"spend_cap": {"max_budget_micro_cents": 500000000, "budget_window": "monthly"}}'

The two halves travel together — a ceiling with no window never enforces — and GET /v1/keys/{id} reads the cap back. A cap larger than your tenant's own monthly allowance is refused. Omitting spend_cap leaves the existing one alone; a cap cannot be removed, only changed.

tpm_limit, models, blocked_models and ip_allowlist are enforced at request time but no route sets them yet — today they are operator-written columns. Spend caps are wired end to end and exercised in production.

Rotation

bash
curl -X POST "$BASE/v1/keys/$KEY_ID/rotate" \
  -H "Authorization: Bearer $SESSION_JWT" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"grace_period_seconds": 86400}'

Both keys work during the grace window, so you can roll a deployment without downtime. The window is in SECONDS and is capped at 24 hours; the body refuses any other field, so a mis-spelled one is a 400 rather than a silent zero-grace cutover. Rotation alone leaves the key alive indefinitely under its new secret — to retire a key, PATCH its expires_at to the end of the grace window in the same breath.