Examples on this page use $API_URL. Set it once, with the /v1 prefix
included — every route lives under it:
export API_URL=https://<your-api-host>/v1Workspaces are administered entirely with tenant credentials. None of these routes take a workspace token.
Check your quota first
curl "$API_URL/quota" \
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"{
"workspaces": { "used": 2, "limit": 10 },
"wallets": { "used": 7, "limit": 40 }
}That is two of the limits your operator set — how many workspaces you may hold,
and how many wallets in total — with your current usage of each. Reading it
before a bulk provisioning run is cheaper than discovering the ceiling halfway
through. wallets.used counts across every workspace the tenant holds, and it
counts the wallet each account is created with, not only the ones you derive.
It does not report how many workspaces may be held open at once. That is a
separate ceiling, enforced by session_capacity_exceeded — raised on workspace
create as well as on mint. No route reports the ceiling up front, but the
refusal itself carries details.scope and details.limit, so the first one
tells you which ceiling you met and what it is.
Create
curl -X POST "$API_URL/workspaces" \
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET" \
-H "content-type: application/json" \
-d '{"slug":"demo","password":"a-strong-workspace-password"}'Returns 201 with the row you will see again in the listing:
{ "workspace": { "slug": "demo", "createdAt": "2026-08-25T09:15:00.000Z", "walletCount": 0 } }Slugs are lowercase letters, digits, and hyphens, starting alphanumeric, up to
63 characters, and con, prn, aux and nul are reserved as directory names
and refused despite fitting the rule. A malformed or reserved slug returns 400
with invalid_slug — the details.expected field says which of the two it was;
a slug already in use returns 409 with workspace_exists.
The password is held to a twelve-character minimum, and it is checked first —
before the slug is reserved, before the quota is consulted, and before any rate
limit is charged. A password that fails the policy returns 422 with
weak_password and costs you nothing else.
Creation is rate-limited per tenant (429, workspace_creation_rate_limited),
and a workspace that was recently deleted cannot immediately be recreated under
the same slug (429, workspace_recreation_cooldown).
List
curl "$API_URL/workspaces" \
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"{
"workspaces": [
{ "slug": "demo", "createdAt": "2025-06-01T09:15:00.000Z", "walletCount": 12 }
]
}Delete
curl -X DELETE "$API_URL/workspaces/demo" \
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"Returns 204. If the workspace currently has an open session, the call is
refused with 409 and workspace_in_use rather than pulling the floor out
from under an active caller.
To delete anyway, opt in explicitly:
curl -X DELETE "$API_URL/workspaces/demo?force=true" \
-H "X-Api-Key: $API_KEY" -H "X-Api-Secret: $API_SECRET"force accepts exactly true or false. Any other value returns 400 with
invalid_parameter — there is no truthy-string coercion, so ?force=1 and
?force=yes both fail. Repeating the parameter (?force=true&force=false)
fails the same way rather than one value winning.
Deleting a workspace destroys its accounts and their key material. If anything inside needs to survive, export it first.
Failures worth handling
| Code | Status | What happened |
|---|---|---|
invalid_body |
400 | Body is not { slug, password }. The message names the offending fields |
invalid_slug |
400 | Slug syntax is wrong |
invalid_parameter |
400 | force was not exactly true or false |
bad_api_key |
401 | Tenant credentials missing or wrong |
weak_password |
422 | Password is below the twelve-character policy |
workspace_exists |
409 | Slug already in use |
workspace_not_found |
404 | No such workspace for this tenant |
workspace_in_use |
409 | Live token leases hold it, or it is mid-open or mid-close; force=true overrides either. On create the same code means the slug is already being held, and create takes no force — retry once it settles |
quota_workspaces_exceeded |
409 | Tenant workspace limit reached |
workspace_creation_rate_limited |
429 | Creating too quickly |
workspace_recreation_cooldown |
429 | Slug was recently deleted |
Both 429s carry details.retryAfterSec and a matching retry-after header —
wait it out rather than retrying immediately.
Related
- Workspaces endpoints — exact shapes
- Authenticate — opening one you created