Errors
Error envelope
API errors return a standard JSON envelope with a human-readable detail and a
stable, machine-readable code:
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
Examples
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/5xxwith the error envelope, at the moment you callPOST /api/v1/orders. Example: an item that is out of stock at create time returns400 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 terminalstatusoffailed(witherror/error_message, e.g.OUT_OF_STOCK, and a fullrefund_amount) orpartial(with arefund_amountfor the undelivered units). You learn this from your webhook orGET /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.