Errors & Retries
One problem shape, stable machine codes, a correlation id on everything — and the retry rules that keep money safe.
The API fails in deliberately boring, uniform ways: one problem shape, stable machine codes, a correlation id on everything, and idempotency where retries matter. This page is the contract your error paths should be written against.
The problem shape
Every non-2xx response is an RFC 9457 problem document:
{
"title": "The request could not be completed.",
"status": 404,
"detail": "Use the error code and correlation ID when contacting support.",
"code": "not_found",
"correlationId": "0HNOB6EOV3MHJ:00000001"
}codeis the stable, machine-readable discriminator — branch on it, never ontitleordetailtext.correlationIdidentifies this exact request in our telemetry. Log it with every failure and quote it in support tickets; it is the difference between "something failed yesterday" and an answer.- Validation failures may additionally carry an
errorsmap of field problems.
Status codes and what to do
| Status | Typical code | Meaning | Your move |
|---|---|---|---|
| 400 | invalid_request | Malformed body, bad parameter, missing Idempotency-Key | Fix the request. Never retry unchanged. |
| 401 | — | Missing/expired/revoked API key | Check key configuration; alert — never loop. |
| 403 | — | Valid key, missing scope (or blocked source address) | Re-issue the key with the right scopes / fix CIDR. |
| 404 | not_found | Resource absent in your workspace | Treat as authoritative; don't retry. |
| 409 | context-specific | Legitimate business conflict: expired/used quote, unavailable product, non-revealable order, over-balance withdrawal | Handle as a domain outcome (re-quote, wait for fulfillment, lower the amount). Retrying unchanged will conflict again. |
| 429 | — | Rate limit exceeded | Honor Retry-After (below). |
| 5xx | — | Transient server fault | Retry with backoff under the same Idempotency-Key. |
Rate limiting
The limit is 120 requests per minute per workspace (fixed one-minute window, no queueing). Excess requests receive 429 with a Retry-After: <seconds> header.
- Spread scheduled jobs (catalog walks, reconciliation sweeps) across the minute instead of firing at
:00. - On
429, sleep the advertisedRetry-After, then resume; add jitter if several workers share the key. - Sustained 429s mean your architecture is polling where it should be consuming webhooks.
Retry rules that keep money safe
- Idempotent mutations, always keyed.
POST /ordersandPOST /orders/{id}/fulfillmentrequire anIdempotency-Key. Retry timeouts/5xx with the same key and you can never double-purchase or lose a response. - The scary case — a timeout on
POST /orders— is already solved. You don't know whether the order was created; resending with the same key returns the existing order if it was, creates it if it wasn't. Never "check first, then maybe resend" — just resend with the key. - Reads are safe to retry (GETs are side-effect free). Use capped exponential backoff with jitter: e.g. 1s, 2s, 4s, 8s (±20%), give up after ~5 attempts into your dead-letter/alerting path.
- Never retry 4xx other than 429 — the outcome is deterministic.
- Withdrawal request retries:
POST /withdrawalshas no idempotency header, so on a timeout read before retrying —GET /withdrawals?status=requested— and only resend if your request is genuinely absent. (Worst case is an extra cancellable withdrawal of your own funds, but the read-first pattern avoids even that.)
Timeouts and connections
- A client timeout of 10–15 s covers every endpoint comfortably; most respond in tens of milliseconds.
- Reuse HTTPS connections (keep-alive); per-request TLS handshakes are wasted latency and burn your rate budget slower than you think.
- Treat DNS/TLS/socket errors exactly like 5xx: retry idempotently with backoff.
An error-handling skeleton
response = send(request with Idempotency-Key where applicable)
switch response.status:
2xx → done
429 → sleep(Retry-After); retry (same key)
5xx / IO → backoff(attempt); retry (same key); after N attempts → dead-letter + alert
409 → domain outcome → dedicated handler (re-quote / wait / adjust)
404 → authoritative absence → reconcile local state
400 → bug in request construction → log correlationId, fail fast, alert
401/403 → credentials/scopes misconfigured → alert operationsWire correlationId through every branch — future-you, and our support team, will thank you.
Updated about 2 hours ago
Did this page help you?