Appearance
Errors
Every non-2xx body is {"error": "<human message>", "code": "<machine code>"} (the 428 confirmation response carries a few extra fields). Match on code, never on the prose — the message may change; the code will not.
code is drawn from a closed enum that evolves additively: a new refusal class adds a member, but no member is ever renamed or removed (a contract test fails CI on a break). So a client can safely switch on it, and an unknown code means a newer server — handle it as a generic failure, don't crash.
Status codes
| status | meaning |
|---|---|
400 | validation / insufficient balance or margin |
401 | missing or unknown key |
403 | compliance refusal, insufficient scope, or a program refusal |
404 | unknown market / order / account, or a route not served here |
409 | idempotency or uniqueness conflict |
428 | confirmation required (first-seen withdrawal destination) |
429 | rate limited (see Rate limits) |
503 | deposits halted — never affects withdrawals |
The code vocabulary
The full set, from the spec's ErrorCode enum (the REST reference links it, and /openapi.json carries it verbatim):
| code | typical status | meaning |
|---|---|---|
validation | 400 | malformed or out-of-range request |
insufficient_balance | 400 | not enough free balance |
insufficient_margin | 400 | order would breach margin |
unauthorized | 401 | missing or unknown API key |
insufficient_scope | 403 | key lacks the route's scope |
compliance_refused | 403 | signup/deposit refused by the compliance gate |
referral_forbidden | 403 | referral action not permitted |
session_limit | 403 | account is at its live-key cap |
not_found | 404 | unknown resource, or route not served here |
conflict | 409 | uniqueness conflict |
idempotency_key_reused | 409 | a key reused on a different request |
confirmation_required | 428 | withdrawal needs a confirmation token |
rate_limited | 429 | a token bucket is exhausted |
deposits_halted | 503 | deposits paused (withdrawals unaffected) |
feature_unavailable | 501 | feature not wired for this deployment |
market_halted | 409/400 | the market is halted |
reduce_only | 409/400 | market is reduce-only; order would increase |
oracle_stale | 409/400 | oracle too stale to price |
oi_cap | 409/400 | open-interest cap reached |
position_limit | 409/400 | position limit reached |
body_too_large | 413 | request body exceeds the cap |
unavailable | 503 | temporarily unavailable |
The status a given code rides can vary with context; the code is the stable signal. Build against code.