Accounts API
An account is a custody vault. See Accounts & vaults for the concepts.
| Method | Path | Scope |
|---|---|---|
POST | /v1/accounts | accounts:write |
GET | /v1/accounts | accounts:read |
GET | /v1/accounts/{id} | accounts:read |
PATCH | /v1/accounts/{id} | accounts:write |
POST | /v1/accounts/{id}/assets/{assetCanonical} | accounts:write |
POST | /v1/accounts/{id}/addresses | addresses:write |
GET | /v1/accounts/{id}/addresses | addresses:read |
GET | /v1/accounts/{id}/balances | balances:read |
GET | /v1/accounts/{id}/balances/{assetCanonical} | balances:read |
DELETE | /v1/accounts/{id} | accounts:write |
Create an account
POST /v1/accounts
{
"label": "merchant-42-hot",
"purpose": "hot",
"vaultType": "web3",
"firstAssetCanonical": "USDT_ETHEREUM",
"externalRef": "mch_42"
}
| Field | Notes |
|---|---|
label | Required, unique per workspace. |
purpose | Optional: hot / warm / cold / escrow / gas / generic (default). |
vaultType | Optional but recommended: web3 (EVM) or asset (non-EVM). Inferred from firstAssetCanonical if omitted. |
firstAssetCanonical | Optional — activates this asset immediately. |
externalRef | Optional, unique per workspace — your own reference (merchant/user id). |
{
"id": "8f1c...uuid",
"workspaceId": "...",
"label": "merchant-42-hot",
"purpose": "hot",
"tags": { "vaultType": "web3" },
"disabled": false,
"createdAt": "2026-05-27T10:00:00.000Z",
"activations": [
{ "id": "...", "accountId": "8f1c...", "assetCanonical": "USDT_ETHEREUM", "status": "active" }
],
"addresses": []
}
:::info Partial success
If the vault is created upstream but the immediate asset activation fails, the
response is still 201 with a firstAssetError: { assetCanonical, message }
field. Retry activation via POST /v1/accounts/{id}/assets/{assetCanonical} —
don't recreate the account, the vault already exists.
:::
Look up an account
GET /v1/accounts accepts exact-match filters — resolve a known vault without
paging the workspace:
| Query | Matches |
|---|---|
?label=merchant-42-hot | the account with that exact label |
?externalRef=mch_42 | the account with that exact external reference |
Both are unique per workspace, so a match returns at most one item.
Activate an asset
Before an account can hold or receive an asset, activate it:
POST /v1/accounts/{id}/assets/USDT_ETHEREUM
→ {
"accountId": "8f1c...uuid",
"assetCanonical": "USDT_ETHEREUM",
"status": "active",
"createdAt": "2026-05-27T10:00:30.000Z"
}
Idempotent — calling again for an already-active asset is a no-op.
Generate a deposit address
POST /v1/accounts/{id}/addresses
{ "assetCanonical": "USDT_ETHEREUM" }
→ {
"id": "...",
"accountId": "8f1c...",
"assetCanonical": "USDT_ETHEREUM",
"address": "0x9a8b...c3d4",
"memo": null,
"status": "active",
"createdAt": "2026-05-27T10:01:00.000Z"
}
memo is populated for memo/tag-based chains (XRP, some Cosmos assets); funds
sent without it may be unattributable.
Read balances
GET /v1/accounts/{id}/balances/USDT_ETHEREUM
→ {
"accountId": "8f1c...",
"assetCanonical": "USDT_ETHEREUM",
"available": "1500.00",
"total": "1500.00",
"fetchedAt": "2026-05-27T10:05:00.000Z"
}
Balances are read live from custody at request time
(fetchedAt is that moment). available is spendable now; total includes
amounts reserved by in-flight transfers. GET …/balances (no asset) returns an
array across all activated assets.
Delete
DELETE /v1/accounts/{id} removes the ZenVault account, its addresses, and
activations. The underlying custody vault is not deleted and any funds there
remain — sweep funds out first. Use delete to prune test/mistake vaults.