Errors
Every error has the same shape. Branch on code — it’s stable; message is for humans and may change.
{ "error": { "code": "insufficient_funds", "message": "Available balance is too low for this payout", "details": null } }| HTTP | code |
Meaning | What to do |
|---|---|---|---|
| 400 | validation_error |
body/query invalid (details lists fields), amount below the minimum, unknown starting_after |
fix the request; don’t retry as-is |
| 400 | invalid_request |
the request couldn’t be read (e.g. malformed JSON) | fix the request |
| 401 | authentication_required |
no Authorization: Bearer sk_… header |
send your secret key |
| 401 | invalid_api_key |
key malformed, unknown or revoked | check the key and its mode |
| 403 | forbidden |
account suspended, or simulate used with a live key |
contact Xaalis / use a test key |
| 403 | merchant_not_active |
live request while your account isn’t active yet |
use test mode until activated |
| 404 | not_found |
no such object for this key’s merchant and mode | check id and test/live key |
| 409 | idempotency_conflict |
Idempotency-Key reused with different parameters (amount, provider, recipient…) |
use a new key for a different request |
| 409 | invalid_state |
the object can’t do that now (payment already final or expired, subscription no longer active, a test advance already running) | read the object’s state; create a new one if needed |
| 409 | key_secret_unavailable |
a key issuance (API key or agent-wallet key) was retried: a secret is only ever shown once | revoke the key id in the error, then issue a replacement with a new Idempotency-Key |
| 422 | insufficient_funds |
payout larger than available (fee included) |
pay out less |
| 422 | unsupported |
e.g. Orange Money payouts in live mode | use Wave |
| 429 | rate_limited |
more than 100 requests/second on one key (60/s per IP on checkout routes) | wait Retry-After seconds, then retry |
| 502 | provider_error |
Wave / Orange Money failed to start the payment | retry, or offer the other method |
| 503 | temporarily_unavailable |
a dependency (ledger, database) timed out or is briefly unavailable — the request may already have taken effect — or a payout/refund outcome isn’t known yet | wait Retry-After seconds, then retry with the same Idempotency-Key (a replay never applies it twice) |
| 503 | provider_not_configured |
that provider isn’t enabled on this platform | offer the other method |
| 500 | internal_error |
our bug; request_id is included |
retry with the same Idempotency-Key; report the request_id |
Quota headers. Responses that passed a rate check carry RateLimit-Policy (each policy’s allowance q per window
w seconds) and RateLimit (remaining r, window t), following the IETF draft
draft-ietf-httpapi-ratelimit-headers-11. Use them to slow down before you hit a 429; they’re a snapshot, not a
promise. Every 429 carries Retry-After, and it wins over the snapshot.
Retry only 429 (after Retry-After), 5xx and network errors — and only with the same Idempotency-Key. Never retry
any other 4xx unchanged.
The merchant dashboard has its own limits, per client IP: 60 requests per second, and 10 identity-document uploads per hour (also counted per business). People behind one shared connection share that allowance. A limited request changed nothing; the dashboard shows the wait (“réessayez dans 30 s”).