Everything here uses a workspace token. Creating and modifying needs the
write scope; listing needs read.
Create an account
curl -X POST "$API_URL/accounts" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"displayName":"Treasury","kind":"HD"}'| Field | Required | Notes |
|---|---|---|
displayName |
yes | Human label; does not have to be unique. Must normalize to 4–64 characters and contain a Latin letter — the slug is derived from it |
kind |
no | HD (default) or PK |
secret |
on PK | Mnemonic for HD, private key for PK. Omit on HD to generate one; required on PK |
defaultNetwork |
no | Network slug this account derives against |
hasOwnPassword |
no | Give the account its own lock, separate from the session |
accountPassword |
when hasOwnPassword |
The account’s own password. Held to the same policy as a workspace password — at least 12 characters |
The response carries the account and whether a secret was generated:
{
"account": {
"slug": "treasury-a1b2",
"displayName": "Treasury",
"kind": "HD",
"hasOwnPassword": false,
"locked": false,
"defaultNetwork": "ethereum",
"wallets": 0
},
"generatedSecret": true
}Derive wallets
A new HD account has none. Derive in bulk:
curl -X POST "$API_URL/accounts/$ACCOUNT/wallets" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"count":5}'{ "before": 0, "after": 5 }count is a positive integer up to 500 per call. The tenant-wide
maxWallets limit still applies — crossing it returns 409 with
quota_wallets_exceeded. Because the response reports both sides, a retry after
an ambiguous network failure is easy to reconcile.
Import a private key
Import needs a PK account. The $ACCOUNT created above is HD, and an HD
account refuses an import with 422 and unsupported_for_kind — the two kinds
are exclusive in both directions, so a PK account equally refuses derive.
Create one for the key:
curl -X POST "$API_URL/accounts" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"displayName":"Imported Key","kind":"PK","secret":"0x…"}'secret is required on a PK account — there is nothing for the service to
generate. Then import further keys into it:
curl -X POST "$API_URL/accounts/$PK_ACCOUNT/wallets/import" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"privateKey":"0x…"}'Adds exactly one wallet and returns it. A key the service cannot parse returns
400 with invalid_private_key.
List wallets and addresses
curl "$API_URL/accounts/$ACCOUNT/wallets" -H "Authorization: Bearer $TOKEN"Each wallet carries its addresses inline:
{
"wallets": [
{
"id": 1,
"tags": ["hot"],
"addresses": [
{ "publicKey": "0xabc…", "vm": "evm", "network": "ethereum", "chainId": 1 }
]
}
]
}For one wallet’s addresses only:
curl "$API_URL/accounts/$ACCOUNT/wallets/1/addresses" \
-H "Authorization: Bearer $TOKEN"Tag wallets
Tags are yours to use; the service attaches no meaning to them. The call replaces the whole list rather than appending:
curl -X PUT "$API_URL/accounts/$ACCOUNT/wallets/1/tags" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"tags":["hot","treasury"]}'Up to 32 tags. To remove them all, send {"tags":[]}.
Accounts with their own password
An account created with hasOwnPassword: true stays locked even while the
workspace is open, and answers 423 with account_locked until unlocked:
curl -X POST "$API_URL/accounts/$ACCOUNT/unlock" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"accountPassword":"…"}'Returns 204. Unlocks last for their own (usually short) window, and failed
attempts are rate-limited — 429 with account_unlock_rate_limited.
Lock it again as soon as you are done:
curl -X POST "$API_URL/accounts/$ACCOUNT/lock" -H "Authorization: Bearer $TOKEN"Both routes accept any workspace token regardless of scope; the scope check still applies to whatever you do next.
Accounts that share the workspace password open transparently on first use, so
they need none of this to get started. They still lock when their custody
window closes, and after that they need exactly this call — with the workspace
password as accountPassword.
The same is true after an explicit lock: POST /v1/accounts/{slug}/lock locks
any account, shared-password or not, and only unlock reopens it.
Delete an account
curl -X DELETE "$API_URL/accounts/$ACCOUNT" -H "Authorization: Bearer $TOKEN"Returns 204 and destroys the account’s key material.
Export first if anything needs to survive.
Related
- Accounts, wallets, and addresses — the model
- Accounts endpoints — exact shapes