Errors
Every error response carries a stable, machine-readable error type and a human-readable message. Branch on error, never on message — the type is a versioned contract, the message is free text and may change at any time.
Error shape
There are two error shapes, depending on the route. Read, list, and delete routes return a compact body keyed by ok. Create routes (POST /v1/orders, POST /v1/deposits, POST /v1/withdrawals, …) return a richer body keyed by success. Both carry the same error type and message, so branching on error works everywhere.
{
"ok": false,
"message": "Order not found",
"error": "ExistenceError"
}
{
"success": false,
"error": "ExistenceError",
"message": "Quote not found",
"errorId": "ExistenceError-1751712000000",
"errorName": "ExistenceError"
}
errorId and errorName appear only on create-route errors. errorId is the errorName plus a timestamp (ExistenceError-1751712000000) — treat both as context for logging and support, and always drive control flow from error.
Error types
The complete set of error values, with the exact HTTP status each type returns.
| Type | HTTP | Meaning | What to do |
|---|---|---|---|
ValidationError |
400 | The request body or parameters failed validation. | Fix the request — check field names, types, required fields, and formats, then retry. |
ExistenceError |
404 | A referenced resource does not exist. | Check the id you sent — the quote, order, or deposit may be wrong or expired. |
StateError |
409 | The resource is not in a state that allows this operation. | Re-read the resource state; wait for or move it to a valid state before retrying. |
DuplicityError |
409 | The operation would create a duplicate resource. | Dedupe — reuse the existing resource rather than resending. |
AuthorizationError |
401 | Missing or invalid credentials, or insufficient permission. Authentication and authorization failures both return 401. | Fetch a fresh token and replay the request once; if it still fails, check the workspace's permissions. |
AllowanceError |
403 | The action exceeds a configured limit or allowance. This is the only type that returns 403. | Check the workspace's limits and permissions; request a higher allowance if you need one. |
FundsError |
402 | Insufficient balance to complete the operation. | Check the workspace balance and top up before retrying. |
ExternalServiceError |
503 | A downstream dependency was temporarily unavailable. | Retry with exponential backoff. On writes, reconcile first — see Retrying a write. |
OperationError |
503 | An unexpected error occurred while processing the request. Also returned when the write rate limit is exceeded. | Retry with backoff; if it persists, contact support with the errorId. |
Rate limits return 503, not 429. Exceeding the write rate limit returns HTTP 503 with OperationError — retry with backoff. A rate-limited request created nothing, so resending it is safe. See Rate limits for thresholds and headers.
Handling patterns
A small decision tree covers almost every case. Switch on error first, then fall back to the HTTP status class.
4xx is your request. A 400 means malformed input and a 404 means a bad id — fix the request before retrying, as an identical retry will fail the same way.
401 → refresh the token. Bearer tokens are short-lived; on a 401 AuthorizationError, fetch a new token and replay the request once. Authentication and permission failures both surface as 401 — if a fresh token still fails, check the workspace's permissions.
409 → state or duplicate. A StateError means the resource moved on — re-read it. A DuplicityError means the resource already exists — reuse it rather than creating another.
5xx and ExternalServiceError are transient. Retry reads freely with exponential backoff. Writes are not de-duplicated — if a create timed out, reconcile before resending or you may create a second payment. See Retrying a write.