Skip to content

Read a workspace, lock and unlock accounts

Reading the open workspace and its accounts, plus the unlock call every account eventually needs once its custody window lapses.

Updated View as Markdown

These routes address the workspace named by the token’s own claim — no workspace-token route takes the workspace in its path, so a path and a token can never disagree about which one is in play. (The tenant-tier route DELETE /v1/workspaces/{slug} does name a workspace in its path; it carries no token to disagree with.)

Auth: workspace token on every route — except GET /accounts/{slug}, which also accepts an account- or wallet-scoped API key bound to that account.

GET /v1/workspace

Current workspace state. Scope: read. Returns 200.

{
  "workspace": "demo",
  "locked": false,
  "accounts": 3,
  "damagedAccounts": 0,
  "expiresAt": "2026-08-18T12:34:56.000Z"
}
Field Meaning
accounts Number of readable accounts. Damaged ones are not counted here
damagedAccounts Count of accounts whose records could not be read
expiresAt When the idle clock currently runs out

The workspace holds accounts + damagedAccounts accounts in total. A damaged record is absent from accounts and from the account listing, so neither is a census — add the two fields when you need one.

GET /v1/accounts

List every account in the workspace. Scope: read. Returns 200.

{
  "accounts": [
    {
      "slug": "treasury",
      "displayName": "Treasury",
      "kind": "HD",
      "hasOwnPassword": false,
      "locked": true,
      "defaultNetwork": "ethereum",
      "wallets": 4
    }
  ]
}

Listing never unlocks anything. Every account reads locked: true after a fresh workspace unlock and stays that way until something opens it — resolving it by slug, or an explicit unlock. Do not read a false here as a precondition for the next call; read it as the custody state at the moment of the listing.

GET /v1/accounts/{slug}

Resolve one account, unlocking it lazily if it shares the workspace password. Scope: read. Returns 200.

{
  "account": {
    "slug": "treasury",
    "displayName": "Treasury",
    "kind": "HD",
    "hasOwnPassword": false,
    "locked": false,
    "defaultNetwork": "ethereum",
    "wallets": 4
  }
}

An account with its own password returns 423 with account_locked until explicitly unlocked.

GET /v1/workspace/assets

Assets on one network. Scope: read. Returns 200.

Query parameters

Parameter Required Notes
network yes Network slug

The network must be named — listing every network’s assets would be an unbounded response shaped by how many networks the tenant has registered.

{
  "network": "ethereum",
  "assets": [
    {
      "id": 1,
      "symbol": "ETH",
      "name": "Ether",
      "decimals": 18,
      "native": true,
      "contractAddress": null
    }
  ]
}

POST /v1/accounts/{slug}/unlock

Reopen an account that is locked. Any scope. Returns 204.

{ "accountPassword": "…" }

An account created with hasOwnPassword: true needs this before its first use, with its own password.

Every other account needs it too, eventually. An account sharing the workspace password opens transparently the first time you touch it, but that opening is a custody window bounded by ttl.accountAbsoluteSec — five minutes by default. When it lapses the account locks, and every route touching it answers 423 and account_locked until you call this one. Pass the workspace password in accountPassword.

The same applies after POST /v1/accounts/{slug}/lock, which locks any account regardless of how it was created.

A wrong accountPassword returns 401 with bad_password.

Failed attempts are rate-limited two ways. A repeated failure on the same account backs off with 429 and account_unlock_rate_limited. Separately, every unlock attempt spends from the same tenant-wide budget token minting draws on, so a burst of unlocks can answer 429 with mint_rate_limited — the same code a mint would return.

Two side effects are easy to miss. An admitted attempt slides the workspace idle clock before the password is checked, so even a wrong password keeps the session from going idle. And re-verifying an account that is already open does not extend its custody window — only a fresh unlock of a locked account starts a new one. After five consecutive failures (or the mint-budget limit, whichever is lower) the whole session is retired, and later calls return 401 with session_expired.

POST /v1/accounts/{slug}/lock

Lock an account again. Any scope. Returns 204. No body.

Errors

Status Code
400 invalid_body, invalid_slug, invalid_parameter, unsupported_network
401 session_expired, bad_password
403 scope_denied
404 account_not_found
423 account_locked
429 account_unlock_rate_limited, mint_rate_limited
Navigation

Type to search…

↑↓ navigate↵ selectEsc close