Errors
MigraPay uses standard HTTP status codes and a consistent JSON envelope to help you diagnose failed requests.
Error response
Failed requests use the same envelope as successful ones. status is "error" and message is a machine-readable error code you can branch on. data adds detail where it helps, often a readable message you can show or log.
{
"status": "error",
"message": "RECIPIENT_INACTIVE",
"data": { "message": "This recipient is inactive. Reactivate it to send money." }
}Validation errors
When the body or query fails validation, data maps each invalid field (as a dotted path) to the first problem found with it:
{
"status": "error",
"message": "VALIDATION_ERROR",
"data": {
"currency": "String must contain exactly 3 character(s)",
"destination.accountNumber": "Account number is required"
}
}HTTP status codes
| Status | Meaning | What to do |
|---|---|---|
| 400 | Bad Request | The request is invalid. Fix the fields listed in data before retrying. |
| 401 | Unauthorized | The API key is missing or invalid. Check the key and its environment. |
| 403 | Forbidden | The key lacks the scope, or the action is not allowed for this key. |
| 404 | Not Found | The resource does not exist in this environment. Check the id. |
| 409 | Conflict | The value is already in use (ALREADY_EXISTS), or an Idempotency-Key clash. |
| 422 | Unprocessable Entity | Well-formed but not possible right now, such as an insufficient balance, an unsupported corridor or an account restriction. |
| 429 | Too Many Requests | You hit the rate limit (RATE_LIMITED). Wait for Retry-After. |
| 500 | Server Error | Something went wrong on our side (SOMETHING_WENT_WRONG). |
| 502 | Bad Gateway | The receiving network rejected or failed the payout. Funds are returned to your wallet. |
| 503 | Service Unavailable | Rates or a payout route are temporarily unavailable. Retry later. |
Common error codes
| Code | Status | Returned when |
|---|---|---|
VALIDATION_ERROR | 400 | Required fields are missing or malformed. |
INVALID_JSON | 400 | The request body is not valid JSON. |
IDEMPOTENCY_KEY_INVALID | 400 | The Idempotency-Key is longer than 255 characters. |
IDEMPOTENCY_KEY_REUSED | 409 | The Idempotency-Key was already used with a different body. |
IDEMPOTENCY_KEY_IN_USE | 409 | A request with the same Idempotency-Key is still running. |
RATE_LIMITED | 429 | Too many requests. See Rate limits. |
missing_api_key | 401 | No API key was sent. |
invalid_api_key | 401 | The API key is unknown or revoked. |
insufficient_scope | 403 | The key lacks the scope for this endpoint. |
INSUFFICIENT_BALANCE | 422 | The source wallet cannot cover the payout and its fees. |
AMOUNT_BELOW_MINIMUM | 400 | The payout is under the corridor minimum (data.min, data.currency). |
AMOUNT_ABOVE_MAXIMUM | 400 | The payout is over the corridor maximum (data.max, data.currency). |
UNSUPPORTED_CORRIDOR | 422 | Payouts to this currency or country are not available. |
RAIL_NOT_AVAILABLE | 422 | The payout method is not available for this currency. |
INSTITUTION_UNKNOWN | 422 | The bank or mobile money code is not recognised. Use GET /payouts/banks. |
ACCOUNT_NOT_VERIFIED | 422 | The recipient's bank could not confirm the account (NGN bank recipients and payouts, validate-account). data.message is a sentence you can show your user. Check the bank code and account number. See Bank-confirmed names. |
WALLET_NOT_FOUND | 404 | You have no wallet in that currency. |
RECIPIENT_NOT_FOUND | 404 | No recipient with that id in this environment. |
TRANSACTION_NOT_FOUND | 404 | No payout or transaction with that id or reference. |
REQUEST_NOT_FOUND | 404 | No collection with that id. |
business_restricted | 422 | Payouts are paused on your account. Contact support. |
limit_exceeded | 422 | The payout is over a limit set on your account. |
PAYOUT_FAILED | 502 | The receiving network declined the payout. Your wallet is credited back. |
RATES_UNAVAILABLE | 503 | Exchange rates are temporarily unavailable. |
TEMPORARILY_UNAVAILABLE | 503 | This route is briefly unavailable. Retry later. |
UPSTREAM_UNAVAILABLE | 503 | We could not reach the payout network. Retry later. |
SOMETHING_WENT_WRONG | 500 | An unexpected error. |
Each endpoint page lists the errors specific to it.
Retry guidance
- Do not retry 4xx errors unchanged. Fix the request first. The exception is 429: wait for
Retry-After, then retry. - Retry 500 and 503 with exponential backoff for reads, and for
POST /collectionsandPOST /payoutssent with anIdempotency-Key. - Never retry a payout sent without an
Idempotency-Keyuntil you have checkedGET /payouts. A timeout does not mean it failed, and you may pay twice. - Log the full response body (status,
messageanddata) of every failed call; include it when you contact support.