---
title: "Mint and introspect scoped API keys"
description: "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."
---

> Documentation Index
> Fetch the complete documentation index at: https://tee.hypetrade.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Mint and introspect scoped API keys

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

```json
{
  "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

```json
{
  "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](#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`](#get-v1authwhoami).

| 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) |

> **A scoped key never signs or manages**
>
> The `write` and `sign` scopes ride on the lease (and show in `whoami`), but a
> scoped key is confined to its bound account's read routes plus export. Signing
> and account/wallet management require a **workspace token** — no scoped-key tier
> reaches `/v1/sign/*` or the write routes. So `unlimited` over `basic` buys a
> scoped key exactly one thing: 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](/guides/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.

> **Deleting a bound account**
>
> A key is bound to its account by slug, and a slug can be reused after the account
> is deleted. Deleting an account that a live key is bound to is recorded in the
> audit log, because a later account created with the same slug would inherit that
> key's authority.

## `GET /v1/auth/whoami`

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

```json
{
  "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.

## Related

- [Scoped API keys](/concepts/scoped-api-keys/) — the durable-key model behind these routes
- [Scopes and tiers](/concepts/scopes/) — the capability model
- [Export key material](/guides/export-key-material/) — the client-unseal flow
- [Workspaces and tokens](/concepts/workspaces-and-tokens/) — the session model

Source: https://tee.hypetrade.xyz/api/api-keys/index.mdx
