Token Vault
Reference

Scopes, Errors & Exit Codes

All 18 API key scopes, the scope error codes, and the tvault CLI exit codes.

Every tvkey_… API key and scoped tvagent_… agent carries a list of scopes. A call without the needed scope fails with 403 SCOPE_DENIED and names the scope in missingScope. For the concepts, see API Keys & Scopes.

Scopes

ScopeAllows
credentials:readFetch credentials the principal has grants for
mcp:useCall MCP proxies the principal has grants for
tokens:listList token metadata (no values)
tokens:createCreate tokens. No read-back
tokens:create-readCreate tokens and receive a read grant on them at creation time. Actually reading the token also needs credentials:read. Not retroactive. Removing the scope stops new auto-grants; existing grants stay
tokens:updateUpdate any token. Human-grant-only
tokens:deleteDelete any token. Human-grant-only
agents:readList and show agents
agents:createCreate agents. Children get a subset of the creator's scopes
agents:manageEdit, suspend, resume, and end sessions of any agent. Human-grant-only
agents:deleteDelete any agent, singly or in bulk. Human-grant-only
grants:writeGrant tokens to agents, only tokens the granter can itself read, so it also needs credentials:read. Auto read grants from tokens:create-read can be passed on, but every copy lapses, for good, when the token is re-created, replaced, rebound or deleted. A grant passed on can't outlive the granter's own. Human-grant-only
proxies:readList and show MCP proxies
proxies:writeCreate and change MCP proxies. Changing a webhook proxy's upstream, headers or service also needs credentials:read and a grant on that service; a session proxy's upstream is human-only. Human-grant-only
policies:readList and show policies
policies:writeCreate, change, and attach policies. Human-grant-only
keys:manageCreate, list, edit, suspend, and resume keys. Human-grant-only
keys:revokeRevoke (delete) any key. Human-grant-only

Human-grant-only scopes can only be put on a key or agent by a signed-in human. A key or agent that tries to pass one on gets 403 SCOPE_NOT_DELEGABLE, even if it holds the scope. The check covers newly added scopes only; narrowing a key that already has them is fine. Every scope covers the whole account, whoever holds it. Other scopes can be passed down as a subset of the holder's own.

Classic agents hold credentials:read and mcp:use implicitly, bounded by their grants. Humans hold every scope.

Inspect what a principal has:

tvault whoami
curl https://api.tokenvault.uk/api/agents/whoami -H "Authorization: Bearer $TVAULT_KEY"

API

Method + pathPurpose
POST /api/keysCreate a key: {name, scopes, expiresAt}. Returns key once. Humans need a sign-in within 5 minutes (401 REAUTH_REQUIRED)
GET /api/keysList keys
GET /api/keys/{id}One key, with its grants
PATCH /api/keys/{id}{name?, status?, scopes?}. Affects only this key. A scopes edit needs an unlocked vault
DELETE /api/keys/{id}Revoke this key only (needs keys:revoke). Keys and agents it created keep working until you revoke them
POST /api/keys/{id}/rotateNew key value. A key may rotate itself; rotating another principal's key is human-only. A lost race returns 409 ROTATION_CONFLICT
POST /api/keys/{id}/grants{serviceName, expiresInHours?, refreshPolicy?}
DELETE /api/keys/{id}/grants/{serviceName}Remove a grant
POST /api/agentsAdd kind: "classic" | "scoped" and scopes
POST /api/agents/{id}/rotate-keyNew agent key. Same rule as key rotation
DELETE /api/agents/{id}Delete an agent. Needs agents:delete (bulk delete too)

Errors

{"detail": {"code": "SCOPE_DENIED", "message": "Missing scope tokens:create", "missingScope": "tokens:create"}}
CodeHTTPCLI exitMeaning
SCOPE_DENIED4038The key lacks the scope in missingScope
HUMAN_ONLY4039Only a signed-in human can do this
KEY_EXPIRED40110The key is past its expiry. Rotate or create a new one
KEY_SUSPENDED40311The key itself is suspended
INVALID_KEY401 (403 on GET /api/agents/credentials, kept for existing agents)12Unknown, revoked, or malformed key
SCOPE_NOT_DELEGABLE4031A key or agent tried to give a human-grant-only scope to another key or agent. scopes lists the offenders
SELF_CHANGE_FORBIDDEN4031A key or agent tried to loosen its own controls: its scopes, its MCP access, resuming itself, its attached policies, or its grants. Suspending, renaming, adding a policy, and rotating its own key are allowed
ROTATION_CONFLICT4091Two rotations of the same key raced and this one lost. No new key was issued
GRANT_REQUIRED403You need a live grant of your own on the token to pass it on or bind a proxy to it. Also returned by /api/proxy/mcp when a proxy was bound from a tokens:create-read grant and the token's creator no longer owns the token
MANUAL_GRANT_IN_PLACE4091A key or agent tried to replace a target's regular grant with an auto-derived copy. A human can
AUTO_GRANT_IN_PLACE4091A key or agent tried to replace a target's auto-derived grant with a regular one. A human can
HEADERS_REQUIRED4001A key changed a proxy's upstream URL without sending new headers in the same edit
NO_GRANT404No grant for that service
UNKNOWN_SCOPEScope name not in the table above
INVALID_EXPIRYExpiry in the past. An expiry later than the creator's is not an error: it is silently clamped to the creator's expiry at creation
TOKEN_EXISTENCE_CHECK_UNAVAILABLE5031Could not confirm the token exists (webhook unreachable). Retry
VALUE_NOT_ALLOWED4001A key tried to send a credential value through Token Vault. Request a store ticket and send the value straight to your webhook
SELF_GRANT_FORBIDDEN4031A key or agent tried to grant itself access
TOKEN_OWNED4091That token name belongs to someone else, or still has proxies bound to it; a key can't take it over or re-create it. Also returned to a key retrying a store ticket for its own new name when references from an older claim (even its own) still exist
TOKEN_OWNERSHIP_UNAVAILABLE5031Couldn't verify who owns the token. Nothing was stored; retry. Also returned when: a key or agent overwrites a token and the creator record can't be read; a take-over finds the creator changed meanwhile; a delete can't write its cleanup mark (refused before anything is deleted); an overwrite hits a token whose earlier delete cleanup is unfinished and still fails; or a webhook bind or rebind can't reset key-created grants and proxies. In every case nothing is stored; retry
POLICY_DENIED403 / 429An attached policy said no

Every key and agent is independent. A key's effective scopes are its own scopes, and its access depends only on its own status, expiry, grants, and attached policies. Revoking, suspending, or narrowing one key never affects another, including keys it created. Revoking a compromised key does not revoke what it created: use the "Created by" information to find those and revoke them yourself. A creator can't give a child scopes, grants, or an expiry beyond its own, but that check happens only at creation.

POST /api/vault/credential-ticket and the TOTP code endpoint are human-only. Keys and agents read credentials through GET /api/agents/credentials?service=<name>.

A locked vault returns 423 VAULT_LOCKED for every key.

Branch on the exit code in scripts:

tvault tokens create --service stripe --value "$V"
case $? in
  0)  echo ok ;;
  8)  echo "key needs tokens:create" ;;
  10) echo "key expired, rotate it" ;;
  11) echo "key suspended" ;;
  12) echo "bad key" ;;
esac

On this page