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), not detail (human-facing text
that may change).
A few errors add machine-readable fields next to detail and code — for
example a price_mismatch carries the real price:
Read those extras only for the code that documents them, and tolerate unknown
keys — new ones can be added without a breaking change.
The token-exchange endpoint (POST /v1/auth/token/client) also emits a
legacy error key alongside the envelope — e.g.
{"error": "Invalid credentials", "detail": "Invalid credentials", "code": "invalid_credentials"}.
Branch on code there too; error is only kept for older integrations.
detail is a plain human-readable fallback and never carries internal or
infrastructure information. A 5xx therefore reports only that the service is
unavailable — the underlying cause stays in our logs, not in your response body.
Status codes
Examples
Create-time errors vs. async failures
There are two 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 when you callPOST /v1/orders. Example: an item 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. The order settles on a terminalstatusoffailed(with anerrorcode — see Order error codes — and a fullrefund_amount) orpartial(with arefund_amountfor the undelivered units). You learn this from your webhook orGET /v1/orders/{id}.
Rule of thumb: a 201 means the order exists and was charged — watch its
status, not the HTTP response, for the outcome. A 4xx means nothing
happened; fix the request and retry.
Order error codes
When an order settles as failed (or partial), its error field carries a
stable, machine-readable code and error_message carries a human-readable
description. Branch on error (stable); show or log error_message (text
that may change). Both appear on GET /v1/orders/{id} and in the
webhook event.
Some of these conditions are usually caught up front at create time and returned
as a 4xx instead (see Status codes); when they are only
detected during fulfillment, they surface here as the order’s error.
On a failed order the charge is fully refunded (refund_amount equals the
price). On a partial order only the undelivered units are refunded, and any
delivered codes are still returned.
Retrying safely
Always send a unique merchant_order_id on POST /v1/orders. It is unique
per account, so a retry (after a timeout or 5xx) that reuses the same value
won’t place a duplicate order — a duplicate is rejected with a
400 validation_error.