Error Codes
Every error code the platform returns, and the fix for each.
| HTTP | code | Meaning & fix |
|---|---|---|
| 401 | MISSING_KEY | Authorization header absent or malformed |
| 401 | EXPIRED_TIMESTAMP | Clock more than 5 minutes off — sync your server to NTP |
| 401 | INVALID_SIGNATURE | Check the full path and that you signed the exact bytes sent |
| 403 | INVALID_KEY | Key not found or revoked |
| 403 | IP_BLOCKED | Calling IP is not on the key's whitelist |
| 403 | FORBIDDEN | Key lacks the required permission for this product |
| 400 | MISSING_PARAM | A required field is absent — the message names it |
| 400 | INSUFFICIENT_BALANCE | Wallet too low for this amount |
| 400 | INVALID_TPIN | Wrong TPIN — locks after 5 consecutive failures |
| 409 | BILLER_PARAMS_CHANGED | BBPS biller changed its fields; re-read /biller/{id} |
| 410 | INVALID_BILL_TOKEN | bill_token expired (15 min) or already spent — fetch again |
| 422 | BANK_NOT_SUPPORTED | Free DMT is not enabled for that bank — read GET /ext/v1/dmt/banks |
| 422 | AGT_UNSUPPORTED | Biller not enabled for the agent channel |
| 422 | BILL_FETCH_FAILED | Mandatory-fetch biller returned no bill |
| 429 | RATE_LIMITED | Per-minute quota exceeded for this key |
| 429 | BILLER_BUSY | Biller concurrency limit — retry after a few seconds |
| 429 | PAYMENT_IN_PROGRESS | Previous payment still in flight — retry with the same key |
| 503 | SERVICE_DISABLED | Category or service temporarily disabled by the operator |
Retrying a timeout
A request that times out may still have succeeded. Retry it with the same client_reference — every money endpoint (Free DMT /transfer, BBPS /bill/pay and each verification) returns the original transaction with duplicate: true rather than charging twice. Sending a new reference for the same intent is what double-charges, so keep one reference per business action and reuse it for every attempt.