API reference

Every endpoint on the Business API, generated from the running service.

Everything under this section is generated from /openapi-b.json, which the API builds from its own mounted routes. A page here cannot describe an endpoint that does not exist, and an endpoint that changes shape changes here on the next build.

Pages are grouped by the object they act on — Charges, Payouts, Invoices — not by URL, so you can find things by what you are trying to do.

Before you start

Base URLs

Sandboxhttps://sandbox.inversepay.app/api/b/v1
Livehttps://api.inversepay.app/api/b/v1

Every request carries Authorization: Bearer <your key>. See Authentication.

Every write carries Idempotency-Key. Retrying with the same key returns the original object rather than creating a second one.

Every amount is in minor units. 5,000 RWF is 500000.

Errors

One shape, everywhere on this surface:

{
  "type": "invalid_request",
  "code": "INVALID_REQUEST",
  "message": "A payout must be for a positive amount.",
  "request_id": "01J..."
}

type is a closed set, and it is the field to branch on:

Statustype
400invalid_request
401unauthenticated
403forbidden
404not_found
405method_not_allowed
409conflict
410gone
413payload_too_large
422validation_error
429rate_limited
500internal_error
502upstream_error
503unavailable
504upstream_timeout

code is machine-readable, never absent, and more specific than the type. An endpoint that has a name for what went wrong uses it — LIMIT_EXCEEDED, INSUFFICIENT_BALANCE, BENEFICIARY_BLOCKED, INSUFFICIENT_AUTHORITY — and only where none is supplied is one derived from the status. Branch on it: that is what it is for, and it is the difference between "this request was refused" and "this particular limit was hit".

Some errors carry more than the four fields. A refusal that a business can act on says what would make it succeed, alongside the shape above:

402
{
  "type": "invalid_request",
  "code": "INSUFFICIENT_BALANCE",
  "message": "This business holds 2,000,000 RWF and the payout needs 15,000,000.",
  "request_id": "01J...",
  "required_minor": 15000000,
  "currency": "RWF"
}

The extra keys sit at the top level rather than nested, and which ones appear depends on the code — LIMIT_EXCEEDED names the limit and what has been used, BENEFICIARY_BLOCKED names the screening status that refused it. Read them when the code tells you they are there; never require them.

message is for a person and is the only field whose wording may change. Never branch on it.

request_id is also returned as the X-Request-ID header, and you can send your own — we echo what you send rather than generating a second id, so one request has one id in your logs and ours. Quote it to support and we can find the exact request.

Try it here

Every operation page has a playground that sends a real request. Point it at sandbox with a sk_test_ key and nothing you do can move money.

Prefer your own tools? The whole surface is a Postman collection, generated from the same spec, with sandbox pre-wired.

On this page