Error handling
The stable error contract and what your bot should do for each case
Error bodies are {error, code?}. Match on the HTTP status and the
machine-readable code; the error string is for logs and humans — its
wording may change without notice.
The matrix
| HTTP | code | Meaning | Bot action |
|---|---|---|---|
| 400 | — | invalid body or query (zod validation text in error) — e.g. an RFQ naming an unknown instrument or an unreachable maker | fix the request, don't retry as-is |
| 400 | BELOW_MIN_NOTIONAL | POST /rfq: the ticket is under the desk minimum (GET /config → minNotionalUsd) | raise the quantity |
| 401 | — | unknown API key | fix config, don't retry |
| 403 | — | not invited to the RFQ / not your quote / not your RFQ | drop, don't retry |
| 404 | — | RFQ, quote, transfer or action not found (closed, expired) · a withdrawal of an unknown instrument | drop, don't retry |
| 409 | RETRY_LATER | transient precondition: a quote's allocations still landing, a withdrawal executing | retry with backoff |
| 409 | INSUFFICIENT_FUNDS | your free holdings cannot cover it (a quote's allocation, your leg of an accept, a withdrawal); nothing was recorded | top up, then retry |
| 409 | DUST_TRADE | a leg (typically the fee) rounds to zero at 10 decimals | don't quote this ticket |
| 409 | (none) | deadline passed · the quote is settling or no longer available · an explicit validUntil in the past or past the deadline · a settlement already in flight on the RFQ · contract contention · on POST /tx/execute, an action whose contract is spent or whose trade no longer settles (it leaves the queue) | read error, usually drop |
| 410 | — | an action or a registration expired before you signed | start that step over |
| 429 | — | per-IP limit on /maker/register/start or /maker/challenge | back off |
| 501 | — | a withdrawal on the local sandbox (no token-standard registry there) · a faucet draw where no faucet runs | — |
| 502 | — | the ledger rejected the command | read error; retry only after changing something |
| 503 | — / RETRY_LATER | ledger unavailable / busy / auth hiccup, or a write the desk could not persist | retry with backoff |
Batch execute: partial failure and retries
A signatures batch on POST /tx/execute answers 200 even when entries
fail — check each entry's status. A failed entry carries retryable:
true is transient (ledger unavailable or busy — retry that entry with
backoff), false is terminal for that entry (unknown, expired, contract
gone). The error text is for logs, as everywhere.
The reliable recovery after a failed or timed-out execute is
GET /tx/pending: an action still listed wants its signature again; an
action absent from the list will not run again — it executed, or its flow
died first (it expired, or was cancelled with its quote or trade). A blind
resend of the whole batch would instead report error for every entry that
already succeeded.
WebSocket errors
The stream closes with 4401 on a missing or unknown X-API-Key — and
mid-stream when the key it opened with is rotated. Any other close is
operational — reconnect with backoff; the snapshot restores your state
(see Subscribe to RFQs).
Practical notes
- A 409 on
POST /maker/quotesafter the RFQ deadline is normal racing — RFQ deadlines are short by design. Drop the RFQ and move on. trade.failedon the stream is not an HTTP error but ends the trade the same way: nothing moved, the accepted quote is revoked, and the RFQ stays open for another accept until its deadline.- Malformed frames on the stream should be ignored, not fatal — parse defensively.