Error codes
Errors return {"status": "failed", "code": "…", "message": "…"} with a matching HTTP status. Branch on code and show message to people. Rejected requests are never charged.
Error body
{
"status": "failed",
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient wallet balance."
}Authentication and request
| Code | HTTP | Meaning |
|---|---|---|
| UNAUTHORIZED | 401 | API key is missing, invalid, revoked or expired |
| IP_NOT_ALLOWED | 403 | Request IP is not on the key's or account's whitelist |
| ACCOUNT_SUSPENDED | 403 | Your account is suspended — contact support |
| API_ACCESS_NOT_APPROVED | 403 | API access has not been approved for this account yet |
| SANDBOX_DISABLED | 403 | Sandbox testing is turned off, so ds_test_ keys are rejected |
| SANDBOX_ONLY | 403 | Endpoint needs a ds_test_ sandbox key (POST /sandbox/reset) |
| FORBIDDEN | 403 | You are not allowed to perform this action |
| NOT_FOUND | 404 | Route or resource (bulk order, PIN order, delivery) not found |
| METHOD_NOT_ALLOWED | 405 | Wrong HTTP method for this endpoint |
| RATE_LIMITED | 429 | Over 120 requests / minute for this key, too many invalid key attempts from your IP, or too many webhook tests / resends |
| ERROR | other | Any other HTTP error |
Validation
Validation errors are always HTTP 400 and name the first failing field.
| Code | HTTP | Meaning |
|---|---|---|
| REQUIRED | 400 | A required field is missing, e.g. "phone is required" |
| INVALID_<FIELD> | 400 | A field failed validation, e.g. INVALID_PHONE, INVALID_REFERENCE, INVALID_METER_TYPE |
Purchases
| Code | HTTP | Meaning |
|---|---|---|
| SERVICE_UNAVAILABLE | 503 | The service or biller is disabled or temporarily unavailable |
| SERVICE_NOT_FOUND | 404 | Unknown service category |
| BILLER_NOT_FOUND | 404 | Unknown or inactive biller slug / network |
| PLATFORM_NOT_FOUND | 404 | Unknown betting platform |
| PRODUCT_NOT_FOUND | 404 | Plan id / code not found or not part of this biller |
| PRODUCT_REQUIRED | 400 | This service needs a product_id |
| INVALID_AMOUNT | 400 | Amount is zero or outside the biller's minimum / maximum |
| AMOUNT_REQUIRED | 400 | Amount missing for a variable-amount electricity / internet product or a bulk airtime number |
| INVALID_QUANTITY | 400 | Quantity out of range: exam PINs 1–10, recharge PINs 1–39, gift cards 1–10, eSIMs 1–5 |
| INVALID_RECIPIENTS | 400 | Invalid phone numbers in a bulk SMS or bulk order, or too many SMS recipients |
| TOO_MANY_RECIPIENTS | 400 | Bulk airtime / data order has more numbers than allowed (100 by default) |
| INSUFFICIENT_STOCK | 400 | Not enough PINs in stock |
| VERIFICATION_FAILED | 400 | Meter, smartcard or account number could not be verified |
| DAILY_LIMIT_EXCEEDED | 429 | Daily spend limit for this service reached |
| TIER_MAX_BALANCE / TIER_DAILY_LIMIT / TIER_MONTHLY_LIMIT | 429 | Account tier limit reached — upgrade KYC to raise it |
| INSUFFICIENT_BALANCE | 402 | Wallet balance is too low for this purchase or bulk order |
| WALLET_INACTIVE | 403 | Your wallet is frozen or not active |
| WALLET_BUSY | 409 | Another request is updating your wallet — retry with the same reference |
| TRANSACTION_FAILED | 422 | The provider could not deliver; your wallet has been refunded |
| DUPLICATE | 200 / 202 / 409 | Reference already used — the original transaction (or bulk order, 409) is returned |
| TRANSACTION_NOT_FOUND | 404 | No transaction with this reference |
Webhooks
| Code | HTTP | Meaning |
|---|---|---|
| INVALID_URL | 400 | Webhook URL must be a public HTTPS endpoint |
| WEBHOOK_UNREACHABLE | 400 | Your endpoint did not answer the verify probe with a 2xx status |
| WEBHOOK_NOT_CONFIGURED | 400 | Register a webhook URL before sending a test or resend |
Network errors and timeouts are not errors from us: if you did not get a response, requery the transaction with your reference before retrying.