Errors
Every error is a structured JSON object with a stable type and code. Handle them by code, show the message, and log the request_id.
The API uses conventional HTTP status codes and returns a consistent error object on every failure. Branch your logic on type and code (stable, machine-readable), surface message to your logs, and record request_id so support can trace the exact request.
Error object
json
{
"error": {
"type": "compliance_error",
"code": "screening_blocked",
"message": "Payout blocked by sanctions/blacklist screening",
"param": "destination.address",
"request_id": "req_2b8f1c9d3e4a"
}
}| Field | Description |
|---|---|
type | High-level category (see below). Use it for coarse handling. |
code | Specific, stable machine-readable code. Use it for precise handling. |
message | Human-readable description. For logs and dashboards — do not parse it. |
param | Present when the error is tied to a specific request field. |
request_id | Identifier for this exact request. Include it in support requests. |
Error types
| Type | Typical status | Meaning |
|---|---|---|
invalid_request_error | 400 / 404 / 409 / 422 | The request was malformed, missing a field, or referenced something that doesn't exist. |
authentication_error | 401 | The API key is missing or invalid. |
permission_error | 403 | The key is valid but lacks the required scope, or the org is suspended. |
rate_limit_error | 429 | Too many requests. Back off and retry. |
compliance_error | 403 / 503 | Blocked by screening, limits, or a KYB requirement. |
api_error | 500 / 503 | Something went wrong on our side. Safe to retry with backoff. |
Common codes
| Code | Status | What it means / what to do |
|---|---|---|
api_key_missing | 401 | No key was sent. Add the X-API-Key header. |
api_key_invalid | 401 | Unknown or revoked key. Check the key and its mode. |
scope_insufficient | 403 | The key lacks the scope this endpoint needs. |
org_suspended | 403 | The organization is suspended. Contact support. |
kyb_required | 403 | Live mode needs a verified business. Submit KYB. |
screening_blocked | 403 | The destination was blocked by screening. See Compliance & screening. |
limit_exceeded | 403 | The payout would breach a per-payout, daily, or monthly limit. |
insufficient_balance | 402 | Your organization's balance is below the payout amount. |
unsupported_destination | 422 | The network/asset combination isn't supported. |
idempotency_conflict | 409 | The idempotency key was reused with a different request. |
payout_not_cancelable | 409 | The payout has advanced past the point where it can be cancelled. |
resource_missing | 404 | The resource doesn't exist (or isn't yours). |
sanctions_unavailable | 503 | Screening is temporarily unavailable. In live mode the payout is not sent; retry later. |
Handling errors
javascript
const res = await fetch(url, options);
if (!res.ok) {
const { error } = await res.json();
switch (error.code) {
case "idempotency_conflict":
// You reused a key for a different payout — this is a bug in your code.
throw new Error("idempotency key reused: " + error.message);
case "insufficient_balance":
return notifyOps("Top up the org balance");
case "screening_blocked":
case "limit_exceeded":
case "kyb_required":
return handleCompliance(error); // don't retry — a human must act
case "sanctions_unavailable":
return retryLater(); // transient
default:
if (res.status >= 500) return retryWithBackoff(); // our side, safe to retry
throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}
}Which errors to retry
- Retry with backoff:
429(rate limit) and5xx(api_error,sanctions_unavailable). Always reuse the sameIdempotency-Keywhen retrying a create. - Do not retry:
4xxother than 429 — the request itself needs to change. A blind retry will fail identically. - Never retry
idempotency_conflict— it means your key generation is wrong.
Log request_id on every failure. It is the fastest way for support to find exactly what happened to a given request.