Error Response Format

All RohoPay errors follow a consistent JSON structure:
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description of the error",
    "reference": "RHP-2024-ABC123"
  }
}
The reference field is included when the error is tied to a specific transaction.

HTTP Status Codes

HTTP StatusMeaning
200Request succeeded
201Resource created
400Bad request — validation error in your input
401Unauthorized — missing or invalid API key
403Forbidden — action not permitted (e.g., disburse in test mode)
404Not found — transaction or resource doesn’t exist
409Conflict — duplicate idempotency key with different params
422Unprocessable — request is valid but logic fails (e.g., insufficient balance)
429Too many requests — rate limit exceeded
500Internal server error — contact support
503Service unavailable — provider is temporarily unreachable

Error Codes

Authentication Errors

CodeMeaning
UNAUTHORIZEDMissing or invalid API key
API_KEY_INACTIVEAPI key has been revoked or rotated
ENVIRONMENT_MISMATCHUsing a test key with live params or vice versa

Validation Errors

CodeMeaning
VALIDATION_ERROROne or more fields are invalid (see message for details)
MISSING_IDEMPOTENCY_KEYIdempotency-Key header not provided
INVALID_PHONEPhone number format is incorrect
INVALID_CURRENCYCurrency code not supported
INVALID_AMOUNTAmount is zero, negative, or below minimum
INVALID_CARDCard number, expiry, or CVV is invalid
CARD_EXPIREDCard expiry date is in the past

Business Logic Errors

CodeMeaning
INSUFFICIENT_BALANCEWallet balance too low for this disbursement
DISBURSE_TEST_BLOCKEDDisbursements are not allowed in test mode
DAILY_LIMIT_EXCEEDEDDaily transaction volume limit reached
TEST_QUOTA_EXCEEDEDTest-mode request quota for today exhausted

Provider Errors

CodeMeaning
PROVIDER_ERRORThe payment provider returned an error
PROVIDER_LINE_DOWNProvider network is temporarily unavailable
PROVIDER_TIMEOUTProvider did not respond in time

Idempotency Errors

CodeMeaning
IDEMPOTENCY_CONFLICTSame key used with different request body
DUPLICATE_REQUESTExact same request already processed (returns original response)

Handling Errors in Code

interface RohoPayError {
  code: string;
  message: string;
  reference?: string;
}

async function collectPayment(phone: string, amount: number) {
  const res = await fetch("https://api.rohopay.com/api/v1/collect", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.ROHOPAY_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({ phone, amount, currency: "UGX" }),
  });

  const body = await res.json();

  if (!body.success) {
    const err: RohoPayError = body.error;

    switch (err.code) {
      case "VALIDATION_ERROR":
        throw new Error(`Invalid input: ${err.message}`);
      case "INSUFFICIENT_BALANCE":
        throw new Error("Your wallet balance is too low.");
      case "PROVIDER_LINE_DOWN":
        throw new Error("Payment network is temporarily unavailable. Try again shortly.");
      case "RATE_LIMIT_EXCEEDED":
        await new Promise(r => setTimeout(r, 2000));
        return collectPayment(phone, amount); // retry
      default:
        throw new Error(`Payment failed: ${err.message}`);
    }
  }

  return body.data;
}

Retry Strategy

Error CodeRetry?Wait
VALIDATION_ERRORNoFix your request
UNAUTHORIZEDNoCheck API key
PROVIDER_LINE_DOWNYes30s → 60s → 120s
PROVIDER_TIMEOUTYes10s → 20s → 40s
RATE_LIMIT_EXCEEDEDYes2s → 4s → 8s
INSUFFICIENT_BALANCENoTop up wallet first
500 Internal ErrorYes5s → 15s → 60s
Always use the same idempotency key when retrying the same transaction. Never generate a new key for a retry — that creates a duplicate charge.