· API Docs

Errors & Status Codes

The API uses standard HTTP status codes. Error bodies are always JSON in the same shape.

Error body format

{
  "success": false,
  "data": null,
  "error": "amount must be a multiple of the credit currency's unit step",
  "code": "AMOUNT_NOT_DIVISIBLE",
  "meta": {
    "request_id": "019f2d23-d667-72f2-91de-747a79547402",
    "timestamp": 1783168751
  }
}

Branch on code, not error — error is human prose for logs only. Keep meta.request_id (the same value as the X-Request-Id response header) to share with support.

Common status codes

StatusMeaningWhat to do
200 / 201SuccessUse the data field
400Malformed request or validation failedCheck body/query per details
401Authentication failedCheck API key / signature / timestamp
403No permissionCheck API key permissions in organization settings
404Resource not foundCheck id / path
409State conflict, e.g. duplicate order_ref or idempotency reuseRead code — RESOURCE_EXISTS: order_ref was already used (look up the original via GET ?order_ref=... before anything else); IDEMPOTENCY_REQUEST_IN_PROGRESS / OPERATION_IN_PROGRESS: wait and retry with the same Idempotency-Key; PAYIN_STATUS_TRANSITION_INVALID: status does not allow this action
422Well-formed but rejected by business rulesRead error.code to handle specific cases
429Rate limitRead X-RateLimit-After / X-RateLimit-Remaining from the response headers, then retry with exponential backoff
5xxServer errorRetry with backoff; if it persists, contact support with the X-Request-Id response header

order_ref — duplicate protection

For POSTs that create transactions (payins, payouts), order_ref must be unique per merchant and environment per transaction type, and a used value is never freed — even after the transaction fails, expires, or is cancelled. Reusing a value is rejected with 409 RESOURCE_EXISTS — the API does not return the existing transaction. If a retry is rejected as a duplicate, the original request already created the transaction — look it up with GET /v1/payins?order_ref=... (or GET /v1/payouts?order_ref=...) instead of creating a new reference. Settlements do not use order_ref — they use Idempotency-Key on POST /v1/settlements (optional client_operation_id).

Recommended retry policy

  • Retry only 429, 502, 503, 504, and network errors
  • On 429, read X-RateLimit-After from the response headers and wait before retrying
  • Exponential backoff + jitter starting at ~250 ms, capped at ~8 s
  • Limit max attempts to 3–5
  • Do not retry non-429 4xx — fix the request instead. Exception: 409 IDEMPOTENCY_REQUEST_IN_PROGRESS or OPERATION_IN_PROGRESS — wait a few seconds and retry with the same Idempotency-Key

Getting help

  • Missing webhook? Any HTTP 2xx is a successful delivery, while a 4xx is a permanent rejection and is not retried. Network errors, timeouts, and 5xx responses are retried with exponential backoff through the outbox, up to 20 delivery attempts including the first; there is no fixed 10-minute schedule. If the callback is still missing, check the status with GET /v1/payins/{id} / GET /v1/payouts/{id}. We recommend a reconcile job that polls still-open transactions periodically (e.g. every 1–5 minutes). For a payin in success status, authorized staff can queue a new payin.succeeded delivery from the payin detail page; the manual resend has a new webhook body id.
  • Transaction problems (funds not credited, amount mismatch, duplicate QR): open a ticket with POST /v1/issues, sending issue_type, issue_sub_type and the request_id — there is no formal response SLA; the team picks tickets up in order. Track progress with GET /v1/issues and cancel an unclaimed ticket with PATCH /v1/issues/{request_id}/cancel.
  • Integration problems that are not tied to a transaction (signature failures, webhook configuration, account opening): contact the support channel your organization received during onboarding, and include the X-Request-Id response header from the failing request.