Error codes
Every response carries a code. It is the normative field: map your switch
codes to it, not to the HTTP status, and not to the message text.
message is written for humans and may be reworded. code is contractual and
will not change meaning.
Safe to retry
| Code | HTTP | What to do |
|---|---|---|
INTERNAL_ERROR | 500 | Retry with the same reference |
SERVICE_UNAVAILABLE | 503 | Retry with the same reference |
RATE_LIMITED | 429 | Back off, honour Retry-After, same reference |
REQUEST_IN_PROGRESS | 409 | Your earlier attempt is still settling. Wait, then poll |
A timeout with no response at all belongs in this group. Retry with the same reference — idempotency guarantees at most one credit.
Settle, then retry
| Code | HTTP | Meaning |
|---|---|---|
PARTNER_EXPOSURE_LIMIT_EXCEEDED | 402 | You are at the ceiling on what you may owe at once |
PARTNER_DAILY_CAP_EXCEEDED | 402 | Today's agreed volume cap is reached |
Nothing moved and nothing is wrong with the request. Settle what you owe — or wait for the daily cap to roll over — then retry with the same reference.
Do not retry — fix the request
| Code | HTTP | Meaning |
|---|---|---|
INVALID_REQUEST | 400 | Malformed, or a required field is missing |
AMBIGUOUS_BENEFICIARY | 400 | More than one beneficiary identifier |
UNSUPPORTED_CURRENCY | 400 | Not the currency your account settles in |
AMOUNT_BELOW_MINIMUM | 400 | Under your agreed minimum |
AMOUNT_ABOVE_MAXIMUM | 400 | Over your agreed maximum |
REFERENCE_CONFLICT | 409 | That reference already exists with a different body |
Do not retry — authentication
| Code | HTTP | Meaning |
|---|---|---|
INVALID_SIGNATURE | 401 | Signature mismatch. See Authentication |
STALE_REQUEST | 401 | Timestamp outside the ±2 minute window. Check your clock |
INVALID_API_KEY | 401 | Unknown, inactive or expired key |
REPLAYED_REQUEST | 409 | Nonce reused. Generate a fresh one per attempt |
PARTNER_MISMATCH | 403 | X-Partner-ID disagrees with your key |
PARTNER_SUSPENDED | 403 | Your account is not active. Contact us |
IP_NOT_ALLOWED | 403 | Source address is off your allowlist |
SCOPE_DENIED | 403 | Your key lacks the scope for this endpoint |
Do not retry — the beneficiary or the limits
| Code | HTTP | Meaning |
|---|---|---|
BENEFICIARY_NOT_FOUND | 404 | No such account, number or card |
BENEFICIARY_NO_ACCOUNT | 422 | Found, but not onboarded with a wallet |
BENEFICIARY_INACTIVE | 422 | The account is not active |
BENEFICIARY_BLOCKED | 422 | The account is blocked |
CARD_NOT_ASSIGNED | 422 | The card is not linked to a subscriber |
KYC_LIMIT_EXCEEDED | 422 | Over the customer's per-transaction KYC limit |
MAX_BALANCE_EXCEEDED | 422 | Would push the wallet past its KYC ceiling |
DAILY_LIMIT_EXCEEDED | 422 | Over the customer's daily or monthly limit |
COMPLIANCE_HOLD | 422 | Held for compliance review |
These are about the customer, not about you. Tell them what to do — top up less, complete KYC, contact support — rather than retrying.
Call POST /api/v1/inbound/name-enquiry first and read max_creditable to
avoid most of this class before you debit anyone.
Reversals
| Code | HTTP | Meaning |
|---|---|---|
REFERENCE_NOT_FOUND | 404 | No transaction under that reference |
ALREADY_REVERSED | 409 | That credit was already reversed |
NOT_REVERSIBLE | 422 | The original is not a completed credit |
REVERSAL_WINDOW_EXPIRED | 422 | Past the agreed window, seven days by default |
INSUFFICIENT_SUBSCRIBER_BALANCE | 422 | The customer already spent it |
INSUFFICIENT_SUBSCRIBER_BALANCE is deliberate: a reversal never overdraws a
consumer wallet. That case becomes a manual recovery conversation, and we
would rather tell you plainly than leave a customer at a negative balance.