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
| Sandbox | https://sandbox.inversepay.app/api/b/v1 |
| Live | https://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:
| Status | type |
|---|---|
| 400 | invalid_request |
| 401 | unauthenticated |
| 403 | forbidden |
| 404 | not_found |
| 405 | method_not_allowed |
| 409 | conflict |
| 410 | gone |
| 413 | payload_too_large |
| 422 | validation_error |
| 429 | rate_limited |
| 500 | internal_error |
| 502 | upstream_error |
| 503 | unavailable |
| 504 | upstream_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:
{
"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.