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"
}
  • code is the stable, machine-readable discriminator — branch on it, never on title or detail text.
  • correlationId identifies 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 errors map of field problems.

Status codes and what to do

StatusTypical codeMeaningYour move
400invalid_requestMalformed body, bad parameter, missing Idempotency-KeyFix the request. Never retry unchanged.
401Missing/expired/revoked API keyCheck key configuration; alert — never loop.
403Valid key, missing scope (or blocked source address)Re-issue the key with the right scopes / fix CIDR.
404not_foundResource absent in your workspaceTreat as authoritative; don't retry.
409context-specificLegitimate business conflict: expired/used quote, unavailable product, non-revealable order, over-balance withdrawalHandle as a domain outcome (re-quote, wait for fulfillment, lower the amount). Retrying unchanged will conflict again.
429Rate limit exceededHonor Retry-After (below).
5xxTransient server faultRetry 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 advertised Retry-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

  1. Idempotent mutations, always keyed. POST /orders and POST /orders/{id}/fulfillment require an Idempotency-Key. Retry timeouts/5xx with the same key and you can never double-purchase or lose a response.
  2. 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.
  3. 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.
  4. Never retry 4xx other than 429 — the outcome is deterministic.
  5. Withdrawal request retries: POST /withdrawals has no idempotency header, so on a timeout read before retryingGET /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 operations

Wire correlationId through every branch — future-you, and our support team, will thank you.


Did this page help you?