Errors
Error Reference
Understanding error codes and how to handle them in your integration.
Error Response Format
All API errors return a consistent JSON structure:
{
"success": false,
"error": {
"code": "INVALID_PHONE",
"message": "Invalid phone number format",
"details": "Phone number must be in format 254XXXXXXXXX"
}
}Validation Errors
When multiple fields fail validation, the response includes an errors array:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"errors": [
{ "field": "phoneNumber", "message": "Required field" },
{ "field": "amount", "message": "Must be a positive number" },
{ "field": "orderId", "message": "Must be between 1-50 characters" }
]
}
}HTTP Status Codes
| Code | Name | Description |
|---|---|---|
| 400 | Bad Request | The request was malformed or missing required parameters. |
| 401 | Unauthorized | Invalid or missing API key. |
| 403 | Forbidden | API key does not have permission for this action. |
| 404 | Not Found | The requested resource does not exist. |
| 409 | Conflict | Resource already exists (e.g., duplicate orderId). |
| 422 | Unprocessable Entity | Validation failed. Check the errors array. |
| 429 | Too Many Requests | Rate limit exceeded. Retry after the specified time. |
| 500 | Internal Server Error | Something went wrong on our end. Contact support. |
| 503 | Service Unavailable | Service temporarily unavailable. Retry later. |
M-PESA Payment Errors
| Code | Message | Resolution |
|---|---|---|
INVALID_PHONE | Invalid phone number format | Use format 254XXXXXXXXX (12 digits) |
INVALID_AMOUNT | Amount must be greater than 0 | Provide a positive number |
DUPLICATE_ORDER | Order ID already exists | Use a unique orderId for each transaction |
INSUFFICIENT_BALANCE | Account balance too low | Customer needs to top up M-PESA |
USER_CANCELLED | Customer cancelled the payment | Prompt customer to retry |
TIMEOUT | Payment request timed out | Customer did not respond within 60 seconds |
INVALID_PIN | Wrong M-PESA PIN entered | Customer entered incorrect PIN |
SERVICE_UNAVAILABLE | M-PESA service temporarily down | Retry after a few minutes |
Card Payment Errors
| Code | Message | Resolution |
|---|---|---|
CARD_DECLINED | Card was declined by issuer | Customer should try another card |
INSUFFICIENT_FUNDS | Not enough funds on card | Customer needs sufficient balance |
EXPIRED_CARD | Card has expired | Customer should use a valid card |
INVALID_CVV | CVV verification failed | Customer should re-enter CVV |
INVALID_EXPIRY | Invalid expiry date | Use MM/YY format with future date |
3DS_FAILED | 3DS verification failed | Customer did not complete verification |
FRAUD_SUSPECTED | Transaction flagged as suspicious | Contact support for review |
CARD_NOT_SUPPORTED | Card type not supported | Use Visa, Mastercard, or Amex |
Payout Errors
| Code | Message | Resolution |
|---|---|---|
INVALID_ACCOUNT | Bank account not found | Verify account number and bank code |
INVALID_BANK_CODE | Bank code not recognized | Use valid bank code (e.g., KCB, EQUITY) |
PAYOUT_LIMIT_EXCEEDED | Amount exceeds payout limit | Split into smaller payouts |
INSUFFICIENT_FLOAT | Not enough float balance | Top up your SerixPay wallet |
RECIPIENT_BLOCKED | Recipient account blocked | Recipient should contact their bank |
DUPLICATE_REFERENCE | Reference already used | Use unique reference for each payout |
Rate Limiting
Rate Limits
API requests are limited to prevent abuse. When exceeded, you'll receive a 429 response.
Per API Key
Limits applied per API key across all endpoints.
Test mode100 req/min
Live mode1000 req/min
Per Endpoint
Additional limits on specific endpoints.
init-payment60 req/min
payouts30 req/min
Rate Limit Headers
All responses include headers to help you track your usage:
X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 998 X-RateLimit-Reset: 1705312800
Error Handling Best Practices
Always check the success field
Every response includes a boolean success field. Always check this before processing.
if (!response.success) {
console.error(response.error.code, response.error.message);
// Handle error appropriately
}Implement exponential backoff
For rate limits and temporary failures, retry with increasing delays.
async function retryRequest(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (err.status === 429 || err.status >= 500) {
await sleep(Math.pow(2, i) * 1000);
continue;
}
throw err;
}
}
}Log errors for debugging
Log the full error response including the request ID for support tickets.
console.error('Payment failed:', {
code: error.code,
message: error.message,
requestId: response.headers['x-request-id'],
timestamp: new Date().toISOString()
});Need Help?
If you encounter an error you can't resolve, contact our support team with:
- The full error response (code, message, details)
- The request ID from response headers
- Timestamp of when the error occurred
- The request payload (with sensitive data redacted)