Skip to content

Mint and introspect scoped API keys

The tenant mints a durable, account- or wallet-scoped API key for an end user — its tier, its optional inquiry key for export, and how a Cold Vault account is unlocked at mint.

Updated View as Markdown

A scoped API key is a single opaque token a tenant mints for one end user, bound to one account — or one wallet within it. Unlike a workspace token, it is durable: it keeps working, and keeps its account unlocked, until the service restarts. The end user stores the one string (for example in localStorage) and never sees the workspace password.

POST /v1/auth/api-key

Mint a scoped key. Auth: tenant credentials. Returns 201.

Request

{
  "workspace": "demo",
  "password": "a-strong-workspace-password",
  "account": "trading-desk",
  "walletId": 0,
  "tier": "unlimited",
  "accountPassword": "the-accounts-own-password",
  "inquiryKey": "x25519:<43 base64 chars>=",
  "validationDuration": 14400
}
Field Type Required Notes
workspace string yes Workspace slug
password string yes The workspace password
account string yes The account the key is bound to
walletId integer no Present to scope the key to one wallet; absent binds the whole account
tier "basic" | "unlimited" no Capability tier. Defaults to basic
accountPassword string no Required only for a Cold Vault (own-password) account. At most 256 characters; over-length is 400 invalid_body
inquiryKey string no The end user’s X25519 recipient (x25519:<base64>) that unlocks export. At most 128 characters; over-length is 400 invalid_body
validationDuration integer no Seconds the inquiry key stays valid; defaults to 4 hours, max one year

Response

{
  "token": "eyJhbGciOi...",
  "expiresAt": "2126-09-08T12:34:56.000Z",
  "level": "wallet",
  "workspace": "demo",
  "account": "trading-desk",
  "walletId": 0,
  "tier": "unlimited",
  "durable": true,
  "scopes": ["read", "write", "sign", "export"],
  "sensitiveEnabled": true,
  "functions": ["export"],
  "inquiryExpiresAt": "2026-09-08T16:34:56.000Z"
}

expiresAt is far in the future because the key is durable — it is the JWT’s own expiry, not a working lifetime. What actually ends the key is a restart or an eviction (see Durability). inquiryExpiresAt is present only when an inquiry key was supplied, and it lapses on its own clock even though the key itself does not.

Responses carry cache-control: no-store.

Errors

Status Code
400 invalid_body, invalid_slug
401 bad_api_key, bad_password
403 permission_denied
404 workspace_not_found, account_not_found
423 account_locked — a Cold Vault account and no accountPassword (a wrong one is 401 bad_password)
500 storage_error — the workspace exists in the ledger but could not be opened
429 mint_rate_limited, session_capacity_exceeded, account_unlock_rate_limited

Tiers

A key is minted at one of two fixed tiers. The tier is expressed as the key’s scopes, and it is reported on the mint response and by whoami.

Tier Scopes What a scoped key can do (this release)
basic (default) read, write Read its bound account, wallets, and addresses
unlimited read, write, sign, export The above, plus sealed export (with a live inquiry key)

Export additionally requires a live inquiry key (below), so an Unlimited key without one still cannot export.

The inquiry key and export

Export is the highest-consequence operation, so it is never sealed to an operator key. The end user generates their own X25519 key pair in the browser, keeps the private half, and hands the tenant only the public inquiry key. The tenant passes it at mint; every export the key performs is sealed to it, and opens only for the holder of the matching private key.

  • The inquiry key unlocks a functionality list — today just export.
  • It is time-boxed by validationDuration (default 4 hours). After it lapses, the base key keeps working but export is locked again.
  • Without an inquiry key, sensitiveEnabled is false and export is refused.

See Export key material for the client-side unseal.

Cold Vault accounts

An account may carry its own password — an extra secret beyond the workspace password. To mint a key for one, the tenant relays that password as accountPassword; the service unlocks the account and the durable key keeps it unlocked. The password is used once and never stored.

A missing accountPassword returns 423 account_locked; a wrong one returns 401 bad_password. Repeated wrong attempts back off with 429 account_unlock_rate_limited.

Durability

A scoped key is stay-exposed: while it is live it pins its workspace and its bound account unlocked. The workspace idle and absolute timeouts, and the account’s own custody timeout, do not apply to it. Everything is in memory — nothing about the key is written to disk, and a restart voids every key.

  • Revocation is coarse. There is no per-key revoke. DELETE /v1/auth/token is accepted for a scoped key but does nothing — a restart is what ends it. Plan to re-mint after a restart.
  • Capacity evicts the least-recently-used key. A durable key holds a workspace handle in memory until restart, so handle capacity is finite. When a new key would exceed a tenant’s capacity, the tenant’s least-recently-used durable key is evicted to admit it. An evicted key’s next request fails exactly as after a restart — re-mint it. Eviction is recorded in the audit log.

GET /v1/auth/whoami

Report what the calling token can do. Auth: any token. Returns 200.

{
  "level": "wallet",
  "workspace": "demo",
  "account": "trading-desk",
  "walletId": 0,
  "tier": "unlimited",
  "durable": true,
  "scopes": ["read", "write", "sign", "export"],
  "sensitiveEnabled": true,
  "functions": ["export"],
  "inquiryExpiresAt": "2026-09-08T16:34:56.000Z",
  "expiresAt": "2126-09-08T12:34:56.000Z"
}

tier is present only for a scoped (account- or wallet-level) key; a plain workspace token has no tier. functions is what the inquiry key currently unlocks — empty once it has lapsed. A holder that has forgotten how its key was minted can read all of this back at any time.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close