Errors

The error envelope, status codes and failure semantics
View as Markdown

Error envelope

API errors return a standard JSON envelope with a human-readable detail and a stable, machine-readable code:

Error response
1{
2 "detail": "Insufficient balance",
3 "code": "insufficient_balance"
4}

Branch your error handling on code (stable) rather than detail (human-facing text that may change).

The token-exchange endpoint (POST /api/v1/auth/token/client) uses a simpler shape for auth failures: {"error": "Invalid credentials"} on 401. Every other endpoint uses the detail / code envelope above.

Status codes

HTTPcodeWhen
400validation_errorBad or missing field, unknown/unavailable item (including out of stock at create time), duplicate merchant_order_id, or out-of-range quantity.
400insufficient_balanceYour account balance can’t cover the order.
401not_authenticatedMissing, invalid or expired access token. Re-exchange your API key.
404not_foundOrder doesn’t exist, or isn’t owned by your account.
409already_existsConflicts with an existing resource.
5xxBackend error. Retry with backoff; use a merchant_order_id so retries stay idempotent.

Examples

400 — validation error
1{ "detail": "Item is not available", "code": "validation_error" }
400 — insufficient balance
1{ "detail": "Insufficient balance", "code": "insufficient_balance" }
401 — not authenticated
1{ "detail": "Authentication credentials were not provided.", "code": "not_authenticated" }
404 — not found
1{ "detail": "Not found.", "code": "not_found" }

Create-time errors vs. async failures

There are two distinct kinds of “failure”, and they surface in different places:

  • Create-time (HTTP) errors — the request is rejected and nothing is charged. These come back as a 4xx/5xx with the error envelope, at the moment you call POST /api/v1/orders. Example: an item that is out of stock at create time returns 400 validation_error (“Item is not available”).

  • Async failures (terminal status) — the order was accepted (201, status: "pending") and your balance was charged, but fulfillment later fails or only partially succeeds. This is not an HTTP error. Instead the order settles on a terminal status of failed (with error / error_message, e.g. OUT_OF_STOCK, and a full refund_amount) or partial (with a refund_amount for the undelivered units). You learn this from your webhook or GET /api/v1/orders/{id}.

Rule of thumb: if you got a 201, the order exists and was charged — watch its status, not the HTTP response, for the outcome. If you got a 4xx, nothing happened and you can safely fix the request and retry.

Retrying safely

Always send a unique merchant_order_id on POST /api/v1/orders. It is unique per account, so if a retry (after a timeout or 5xx) reuses the same value, you won’t accidentally place a duplicate order — a duplicate is rejected with a 400 validation_error.