Errors
No `success` flag: the HTTP status is the answer, and the body names the field where there is one.
#The shape
A developer reads an error to find out what to change, so it names the field where there is one. There is no success flag: the HTTP status is the answer.
{
"error": {
"type": "invalid_request",
"message": "customer_reference is required. It is your own id for this customer and we hand it back on every payment.",
"field": "customer_reference"
}
}type is one of unauthorized, account_inactive, not_verified, invalid_request, not_found, conflict, provider_error, not_test_mode, validation.
not_test_mode is returned 403 by the sandbox routes when a live key is used. It is the one guard between simulated money and real money, so it refuses loudly rather than doing nothing.
#The types
type is what you branch on:
| type | usually means |
|---|---|
unauthorized | No key, the wrong kind of key, or a key that is not valid |
account_inactive | The merchant account cannot transact right now |
not_verified | Live mode needs an approved business. Test mode keeps working |
invalid_request | A field is missing or not allowed. field names it |
not_found | No such reference, or it does not belong to your merchant and mode |
conflict | The same thing is already being created. Wait, do not retry immediately |
provider_error | Something upstream failed. Nothing was charged |
not_test_mode | A test-only endpoint was called with a live key |
validation | A field is missing or not on the allowed list. message says which |
#Every code
Some responses carry a code naming the exact refusal. This table covers the whole merchant surface, so a code your operations team sees in the dashboard is here too.
| code | http | what to show |
|---|---|---|
merchant_suspended / merchant_closed | 403 | show message, point at support. Suspension also revokes the dashboard token, so in practice this arrives from auth/merchant-login and an in-flight session gets 401 unauthorized instead |
kyb_required | 403 | the verification gate; message says what is blocked and that test mode still works |
wallet_not_allowed | 422 | a registered business tried to settle to a personal wallet. message explains it and names the business; render it rather than writing your own |
bank_account_required | 422 | settlement pointed at a bank with no account recorded |
validation | 422 | a field is missing or not on the allowed list; message says which |
insufficient_ready | 422 | more than is available to pay out; ready and clearing are on the error |
below_minimum | 422 | under NGN 100 to a wallet or NGN 500 to a bank |
name_unconfirmed | 422 | the receiving bank would not confirm the account name. Nothing was sent |
account_not_found | 422 | the bank cannot find that account number |
payout_failed | 422 | a refusal we could read. The money is back in Ready |
issue_in_progress | 409 | an account for that customer is already being created; wait, do not retry |
insufficient_collected | 422 | a refund or settlement larger than the Collected balance; nothing was written |
provider_error / mail_failed | 502 | the code could not be sent; offer the other channel |
#Two that look like bugs and are not
- A
401on an unknown, revoked or rotated key is one message for all three. Deliberately: otherwise the endpoint could be used to tell a real key from a guess. amount_over_limitonPOST /paymentsis us refusing early rather than your customer watching a transfer bounce at their bank.