API Reference / Errors
Every error response shares one shape (a business error body, not the framework's default validation body):
{
"error": "machine-readable code, e.g. auth_required",
"message": "human-readable text (nullable)",
"recoverable": true
}recoverable=true is safe to retry with backoff; false (invariant_violation with a 500, for example) means internal state is inconsistent and trading is paused — do not retry.
A query carrying minCursor (and the order status query) can return 404 either because the projection has not caught up with the cursor you passed, or because the resource genuinely does not exist. The response bodies are byte-for-byte identical (a fixed literal market_not_found, guaranteed structurally). Querying right after a 202 will most likely 404 first: poll with the same cursor; do not treat the first 404 as terminal.
When a call fails the question is whether you can resend it, not what the code is called. The last two columns are the answer: the retry policy and whether resending is idempotent.
| Status | error | When it happens | Retry | Resend idempotency |
|---|---|---|---|---|
| 400 | invalid_request | Generic invalid request; also covers a cancel that supplies neither orderId nor clientOrderId | Fix, then resend | A read — resending has no side effect |
| 400 | malformed_order_bytes | A place-order carrying an on-chain signature (auth): orderBytes will not decode, or the signature is invalid | Fix, then resend | A read — resending has no side effect |
| 400 | expiration_required / expiration_mismatch / expired_envelope / envelope_exceeded | A signed order's expiration is missing, disagrees with the bytes, has passed, or exceeds the matching cutoff | Fix, then resend | A read — resending has no side effect |
| 400 | signature_required | A market in on-chain mode rejects unsigned orders (migration switch, off by default) | Fix, then resend | A read — resending has no side effect |
| 400 | comment_rejected | The comment hit a blocked term | Fix, then resend | A read — resending has no side effect |
| 400 | invalid_address / invalid_email | Malformed address or email | Fix, then resend | A read — resending has no side effect |
| 401 | auth_required / auth_failed | No session, or signature verification failed — always merged so the two cannot be told apart (anti-enumeration). Sessions are gated twice: 24h idle and a 7d absolute cap | Fix, then resend | A read — resending has no side effect |
| 403 | login_method_disabled | That login method is not enabled (google and email are off by default) | Do not retry | A read — resending has no side effect |
| 409 | display_name_taken | Display names are globally unique and this one is taken | Fix, then resend | A read — resending has no side effect |
| 409 | chain_binding_not_active | The on-chain binding exists but is unusable (status carries the raw state; FAILED and PENDING call for different handling) | Retry with backoff | A read — resending has no side effect |
| 423 | user_blocked | The account is hard-stopped by a manual action or a jurisdiction policy (a freeze-class admission block) | Do not retry | A read — resending has no side effect |
| 429 | code_cooldown / email_rate_limited | Email verification code cooldown or send rate limit; message carries the retry-after seconds | Retry with backoff | A read — resending has no side effect |
| 501 | heartbeat_disabled | Heartbeats are not enabled on this deployment — the request is fine, the capability is off | Do not retry | A read — resending has no side effect |
| 503 | command_log_unavailable / seq_unavailable / duplicate_in_flight | Recoverable write-path failures: the command log is unwritable, sequence issuance is down, or the same clientOrderId is in flight. All are safe to retry with backoff | Retry with backoff | Only with clientOrderId — without it a resend may become two orders |
| 503 | chain_binding_source_unavailable | Clearing is unreachable — this does not mean “this market rejects orders” | Retry with backoff | A read — resending has no side effect |