Skip to content
Version: Current

Keys and grants

2026-11-05. repository reference/snapshot reviewed 2026-11-05; verify current behavior before using as live guidance.

A virtual key is the machine credential of the platform. It is issued once, shown once, stored as a hash, and revoked by deleting one row.

Field Meaning
name operator-facing label; not unique
scopes chat, embeddings, admin
models allowlist of catalog model ids; empty means every model
daily_usd daily spend cap in USD
monthly_usd monthly spend cap in USD
rpm requests per minute
tpm tokens per minute
disabled pause without revoking

The model allowlist is checked before the exact-cache lookup, so a cached premium answer is never replayed to a key that may not call that model. Rate and spend windows live in memory; a restart resets the current minute, day and month windows.

  • Only a SHA-256 hash is stored in the virtual_keys table.
  • The plaintext is returned once by POST /v1/router/keys or by the consumer create flow, and never again.
  • Lists show ids, limits and spend. They never show a key or a hash.
  • A key id (key-…) is not a secret. It is safe to record in a runbook; the vk_… value is not.

Never paste a key value into chat, a ticket, a commit or a documentation page. Reference the file that holds it. Key material lives in the service’s environment file or in the consumer’s own browser session, nowhere else.

Over loopback with an admin key:

Terminal window
curl -s http://127.0.0.1:4020/v1/router/keys \
-H "Authorization: Bearer $ACUMEN_ADMIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"name":"ci","scopes":["chat"],"models":["cheap"],
"daily_usd":5,"monthly_usd":50,"rpm":60,"tpm":200000}'

The response contains the plaintext once. Capture it into the consumer’s secret store immediately; do not echo it into a shell log.

Updating is partial: send only the fields that change.

Terminal window
curl -s -X PATCH http://127.0.0.1:4020/v1/router/keys \
-H "Authorization: Bearer $ACUMEN_ADMIN_KEY" \
-H 'Content-Type: application/json' \
-d '{"id":"key-…","daily_usd":10}'

Revoking is DELETE /v1/router/keys?id=key-…. The next request with that key is 401 invalid virtual key with no restart.

A signed-in person creates a key at /app/grants/new. That handler writes both rows (the api grant and its virtual_keys row) and deletes the key if the grant write fails, so the two cannot drift.

Every credential that can act for an account appears in grants:

  • kind=api: a virtual key, with the same id as its virtual_keys row.
  • kind=session: a browser sign-in, id grt_ plus the session id.
  • kind=delegation: reserved; no handler writes it yet.

Grants carry label, scopes, models, caps, origin, created_by, state and timestamps. State is active, disabled, revoked, or the computed expired. grant_events is the append-only history shown on the grant page.

Scope Doors
chat /v1/chat/completions, /v1/messages, /v1/responses, /v1/search, /v1/models
embeddings /v1/embeddings
admin /health, /readyz, /console*, /metrics, /debug/pprof/*, /v1/router/*, key CRUD

A chat key that reaches an operator path gets 403 with X-Acumen-Denied-By: scope.

Condition Status Header
missing or unknown key 401 X-Acumen-Denied-By: key
scope not held 403 scope
model not on the allowlist 403 model
RPM exceeded 429 with Retry-After rate
TPM exceeded 429, retry after the minute rolls rate
daily or monthly cap crossed 402 budget