Conventions

These apply to every endpoint. Read once.

Response envelope

Every response has the same envelope:

{
  "code": 200,
  "message": "Corridors fetched successfully",
  "data": { },
  "metadata": { }
}
Field Always present Meaning
code yes HTTP status code, mirrored in the body for convenience.
message yes Human-readable summary of the result.
data on success The resource(s) — object or array. Omitted on errors.
metadata on paginated lists Pagination details (see below).

Always branch on the HTTP status code; code mirrors it.

Pagination

List endpoints accept page (1-based) and return a metadata block:

{
  "code": 200,
  "message": "Transactions retrieved successfully",
  "data": [ ],
  "metadata": {
    "page": 1,
    "per_page": 20,
    "total": 134,
    "has_next": true,
    "has_prev": false,
    "total_pages": 7
  }
}

To read the next page, add 1 to page. Continue until has_next is false.

Money & currency

  • All balances, amounts, and fees are in USD. There is one exception: a field that names a different currency. Examples are the currency of a corridor, and the local amount that your customer pays or receives.
  • USD amounts are decimal (e.g. 100.50), not minor units.

Idempotency

Send an Idempotency-Key header on any request that moves money — a unique value you generate per logical operation (a UUID is ideal):

Idempotency-Key: 7f3a1c9e-2b4d-4a6f-8e1c-9d2f5a7b3c4e

The first request runs and we keep its response. Every repeat of that key returns the same response, with the same status, instead of doing the work again. So when a payout times out, retry it with the same key: you either get the original result, or the payout is made — once.

Three cases are worth knowing:

What you send What you get
The same key, the same body The first response, unchanged.
The same key, a different body 422, idempotency_key_reused. Reusing a key for different work is a bug, and returning the first receipt would hide it from you.
The same key while the first is still running 409, idempotency_in_progress. Wait a moment and ask again.

If a request fails without answering, its key is released immediately so your retry runs. If the process handling it dies, the key frees itself after ninety seconds.

Errors

Errors return a non-2xx status and an envelope without data:

{
  "code": 400,
  "message": "account_name (sender name) is required for bank-transfer deposits"
}
Status Meaning
400 Bad Request Invalid or missing parameters. The message says what.
401 Unauthorized Missing, malformed, or revoked API key.
403 Forbidden Authenticated but not allowed to access this resource.
404 Not Found The resource does not exist, or it is not yours.
422 Unprocessable The request is correct, but we cannot accept it. For example, the amount is less than the corridor minimum.
5xx The error is on our side. You can send a read request again. For a write, use an Idempotency-Key.

Branching on an error

message is written for a person, and we reword it whenever we find a clearer sentence — so do not match on it. When a refusal is one you can do something about, the envelope also carries an error_code, and that does not change:

{
  "message": "This account cannot accept payments yet — its settlement account is still being set up.",
  "error_code": "settlement_account_not_ready"
}
error_code What happened
settlement_account_not_ready The account cannot be paid yet; its settlement account is still coming up. Try again shortly.
corridor_unavailable We do not collect or pay out on this corridor right now.
corridor_not_link_payable Payable through the API, but not through a payment link — its bank transfers arrive into a standing account, which cannot be matched to a single payment.
amount_below_minimum Below the smallest amount the corridor will carry.
insufficient_balance Not enough available balance to cover this.
link_not_payable The payment link is expired, used up, or switched off.
idempotency_key_reused The same Idempotency-Key was sent for different work.
idempotency_in_progress A request with that key is still running.

Errors we do not expect you to handle carry no error_code. That is deliberate: a code is a promise that the meaning will not change, and we only make it where we can keep it.