Error Codes
Every error response follows a consistent envelope. This page lists all HTTP status codes and application-level error codes.
Error Response Format
{
"success": false,
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": [],
"timestamp": "2026-02-27T14:22:00Z",
"path": "/api/v1/transfers/external",
"method": "POST"
}
| Field | Type | Description |
|---|---|---|
success | boolean | Always false for errors |
error | string | Human-readable description |
code | string | Machine-readable error code |
details | array | Validation errors or additional context |
timestamp | string | ISO 8601 timestamp |
path | string | Request path |
method | string | HTTP method |
HTTP Status Codes
| Status | Title | When It Occurs |
|---|---|---|
400 | Bad Request | Invalid input, missing required fields, malformed JSON |
401 | Unauthorized | Missing token, expired token, invalid token |
403 | Forbidden | Insufficient permissions, feature disabled, wallet frozen |
404 | Not Found | Resource doesn't exist |
409 | Conflict | Duplicate entry (e.g. beneficiary already exists) |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Unexpected server failure |
503 | Service Unavailable | Dependency unavailable (NIBSS, Interswitch) |
Application Error Codes
Authentication
| Code | HTTP | Description |
|---|---|---|
INVALID_CREDENTIALS | 401 | Email or password incorrect |
UNAUTHORIZED | 401 | No authorization token provided |
INVALID_TOKEN | 401 | Token is malformed or signature verification failed |
TOKEN_EXPIRED | 401 | Access token has expired — refresh it |
ACCOUNT_LOCKED | 403 | Account locked after too many failed login attempts |
MFA_REQUIRED | 403 | 2FA code required but not provided |
INVALID_MFA_CODE | 401 | 2FA code is incorrect or expired |
Validation
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | One or more fields failed validation. Check details array. |
MISSING_FIELD | 400 | Required field not provided |
INVALID_FORMAT | 400 | Field value doesn't match expected format |
Tenant
| Code | HTTP | Description |
|---|---|---|
TENANT_NOT_FOUND | 404 | The specified tenant ID doesn't exist |
TENANT_INACTIVE | 403 | Tenant account is disabled |
FEATURE_DISABLED | 403 | Requested feature is not enabled for this tenant |
Accounts & Wallets
| Code | HTTP | Description |
|---|---|---|
USER_NOT_FOUND | 404 | User doesn't exist in this tenant |
ACCOUNT_NOT_FOUND | 404 | Wallet or account doesn't exist |
WALLET_FROZEN | 403 | Wallet is frozen — debits are blocked |
WALLET_INACTIVE | 403 | Wallet has been deactivated |
Transfers
| Code | HTTP | Description |
|---|---|---|
INSUFFICIENT_FUNDS | 400 | Wallet balance is less than transfer amount + fees |
LIMIT_EXCEEDED | 400 | Daily or monthly transaction limit exceeded |
TRANSFER_FAILED | 500 | Transfer processing failed at the provider |
INVALID_ACCOUNT | 400 | Recipient account number is invalid |
INVALID_BANK_CODE | 400 | Bank code not recognized |
FRAUD_DETECTED | 403 | Transaction blocked by fraud detection |
DUPLICATE_TRANSFER | 409 | Identical transfer submitted within dedup window |
Loans
| Code | HTTP | Description |
|---|---|---|
LOAN_NOT_ELIGIBLE | 400 | User doesn't meet eligibility criteria |
LOAN_LIMIT_EXCEEDED | 400 | Requested amount exceeds maximum for this product |
EXISTING_LOAN | 409 | User has an outstanding loan that must be repaid first |
APPLICATION_NOT_FOUND | 404 | Loan application doesn't exist |
Savings
| Code | HTTP | Description |
|---|---|---|
MINIMUM_AMOUNT | 400 | Deposit is below the product minimum |
EARLY_WITHDRAWAL | 400 | Cannot withdraw from locked savings before maturity |
SAVINGS_NOT_FOUND | 404 | Savings account doesn't exist |
Bill Payments
| Code | HTTP | Description |
|---|---|---|
BILLER_NOT_FOUND | 404 | Biller ID is invalid |
CUSTOMER_NOT_FOUND | 400 | Customer reference not recognized by biller |
PAYMENT_FAILED | 500 | Payment processing failed at the provider |
Rate Limiting
| Code | HTTP | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Too many requests — wait and retry |
General
| Code | HTTP | Description |
|---|---|---|
DUPLICATE_ENTRY | 409 | Resource already exists |
INVALID_REFERENCE | 400 | Foreign key or reference doesn't exist |
INTERNAL_ERROR | 500 | Unexpected server error |
SERVICE_UNAVAILABLE | 503 | External dependency is down |
Common Error Scenarios
Missing X-Tenant-ID header
{
"success": false,
"error": "X-Tenant-ID header is required",
"code": "TENANT_NOT_FOUND"
}
Fix: Include the X-Tenant-ID header in every request.
Expired Token
{
"success": false,
"error": "Token has expired",
"code": "TOKEN_EXPIRED"
}
Fix: Call POST /api/v1/auth/refresh with your refresh token to get a new access token.
Insufficient Funds
{
"success": false,
"error": "Insufficient funds for this transaction",
"code": "INSUFFICIENT_FUNDS",
"details": {
"required": 100100.00,
"available": 50000.00
}
}
Fix: Ensure the source wallet has enough balance to cover the amount plus fees.
Validation Error
{
"success": false,
"error": "Validation failed",
"code": "VALIDATION_ERROR",
"details": [
{ "field": "amount", "message": "Amount must be greater than 0" },
{ "field": "recipientAccountNumber", "message": "Account number is required" }
]
}
Fix: Correct the fields listed in the details array and retry.
Best Practices
- Check
code, noterror— Theerrorstring may change; thecodeis stable. - Retry on 503 — External provider errors are transient. Retry with exponential backoff.
- Never retry 4xx — Client errors require fixing the request before retrying.
- Log the full response — Include
timestamp,path, andmethodin your logs for debugging.