Errors

Our API uses conventional HTTP response codes to indicate the success or failure of an API request. In general, codes in the 2xx range indicate success, codes in the 4xx range indicate an error that failed given the information provided, and codes in the 5xx range indicate an error with our servers.

HTTP Status Codes

Status Code Description
200 OK - The request was successful
201 Created - The resource was successfully created
400 Bad Request - The request was invalid or cannot be served
401 Unauthorized - The request requires authentication
403 Forbidden - The server understood the request but refuses to authorize it
404 Not Found - The requested resource could not be found
422 Unprocessable Entity - The request was well-formed but contains semantic errors
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error - Something went wrong on our end
502 Bad Gateway - Invalid response from upstream server
503 Service Unavailable - The server is temporarily unavailable

Error Response Format

All error responses follow a consistent JSON format:

{
    "success": false,
    "message": "Error description",
    "error_code": "ERROR_CODE"
}

Common Error Codes

Authentication Errors

UNAUTHORIZED

{
    "success": false,
    "message": "Authentication required",
    "error_code": "UNAUTHORIZED"
}

INVALID_TOKEN

{
    "success": false,
    "message": "Invalid or expired access token",
    "error_code": "INVALID_TOKEN"
}

INVALID_CREDENTIALS

{
    "success": false,
    "message": "Invalid username or password",
    "error_code": "INVALID_CREDENTIALS"
}

ACCOUNT_SUSPENDED

{
    "success": false,
    "message": "Merchant account is suspended",
    "error_code": "ACCOUNT_SUSPENDED"
}

Validation Errors

VALIDATION_FAILED

{
    "success": false,
    "message": "Validation failed",
    "error_code": "VALIDATION_FAILED"
}

INVALID_FORMAT

{
    "success": false,
    "message": "Invalid data format",
    "error_code": "INVALID_FORMAT"
}

Business Logic Errors

INSUFFICIENT_BALANCE

{
    "success": false,
    "message": "Insufficient account balance",
    "error_code": "INSUFFICIENT_BALANCE"
}

DUPLICATE_REFERENCE

{
    "success": false,
    "message": "Reference ID already exists",
    "error_code": "DUPLICATE_REFERENCE"
}

INVALID_ACCOUNT

{
    "success": false,
    "message": "Invalid bank account",
    "error_code": "INVALID_ACCOUNT"
}

TRANSACTION_NOT_FOUND

{
    "success": false,
    "message": "Transaction not found",
    "error_code": "TRANSACTION_NOT_FOUND"
}

INVALID_BANK_CODE

{
    "success": false,
    "message": "Bank code is not supported",
    "error_code": "INVALID_BANK_CODE"
}

Rate Limiting Errors

RATE_LIMIT_EXCEEDED

{
    "success": false,
    "message": "Rate limit exceeded. Please try again later",
    "error_code": "RATE_LIMIT_EXCEEDED",
    "meta": {
        "retry_after": 60
    }
}

Server Errors

INTERNAL_ERROR

{
    "success": false,
    "message": "Internal server error",
    "error_code": "INTERNAL_ERROR"
}

SERVICE_UNAVAILABLE

{
    "success": false,
    "message": "Service temporarily unavailable",
    "error_code": "SERVICE_UNAVAILABLE"
}

MAINTENANCE_MODE

{
    "success": false,
    "message": "System is under maintenance",
    "error_code": "MAINTENANCE_MODE"
}

Error Handling Best Practices

1. Always Check HTTP Status Codes

const response = await fetch(url, options);

if (!response.ok) {
    const errorData = await response.json();
    throw new Error(`API Error: ${errorData.message}`);
}

2. Implement Retry Logic for Temporary Errors

function makeApiCallWithRetry($url, $data, $maxRetries = 3) {
    for ($i = 0; $i < $maxRetries; $i++) {
        try {
            $response = makeApiCall($url, $data);
            return $response;
        } catch (Exception $e) {
            if ($e->getCode() >= 500 && $i < $maxRetries - 1) {
                sleep(pow(2, $i)); // Exponential backoff
                continue;
            }
            throw $e;
        }
    }
}

3. Handle Rate Limiting Gracefully

async function handleRateLimit(response) {
    if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 60;
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        return true; // Retry the request
    }
    return false;
}

4. Log Errors for Debugging

function logApiError($response, $requestData) {
    error_log(json_encode([
        'timestamp' => date('c'),
        'request' => $requestData,
        'response' => $response,
        'http_code' => $response['http_code'] ?? null
    ]));
}

5. Provide User-Friendly Error Messages

function getUserFriendlyMessage(errorCode) {
    const messages = {
        'INSUFFICIENT_BALANCE': 'Your account balance is insufficient for this transaction.',
        'INVALID_ACCOUNT': 'The bank account information is invalid.',
        'RATE_LIMIT_EXCEEDED': 'Too many requests. Please try again in a few minutes.',
        'SERVICE_UNAVAILABLE': 'Service is temporarily unavailable. Please try again later.',
        'DUPLICATE_REFERENCE': 'This transaction reference already exists.',
        'INVALID_BANK_CODE': 'The selected bank is not supported.'
    };

    return messages[errorCode] || 'An unexpected error occurred. Please try again.';
}

Debugging Tips

  1. Check Request ID: Use the request_id from error responses when contacting support
  2. Validate Input: Ensure all required fields are present and properly formatted
  3. Test in Sandbox: Always test error scenarios in the sandbox environment
  4. Monitor Logs: Keep detailed logs of API requests and responses
  5. Handle Timeouts: Implement proper timeout handling for network requests

Common Error Scenarios

Disbursement Errors

// Handle disbursement-specific errors
async function handleDisbursementError(error) {
    switch (error.error_code) {
        case 'INSUFFICIENT_BALANCE':
            // Check balance and notify user to top up
            break;
        case 'INVALID_ACCOUNT':
            // Validate account using check-account endpoint
            break;
        case 'DUPLICATE_REFERENCE':
            // Generate new reference ID
            break;
        default:
            // Handle generic error
            break;
    }
}

Authentication Errors

function handleAuthError($errorCode) {
    switch ($errorCode) {
        case 'INVALID_TOKEN':
            // Refresh token by logging in again
            return refreshToken();
        case 'ACCOUNT_SUSPENDED':
            // Contact support
            throw new Exception('Account suspended. Please contact support.');
        default:
            throw new Exception('Authentication failed');
    }
}