Skip to content

Error codes

Every error is JSON in one envelope, with a message written for humans and a request_id you can quote to support:

{
"error": {
"type": "invalid_request",
"code": "parameter_invalid",
"message": "amount: Number must be greater than 0",
"param": "amount",
"request_id": "req_01J8XQ7M4K..."
}
}

Types

typeHTTPMeaning
invalid_request400The request itself is wrong — fix and resend
authentication_error401Missing or invalid API key
permission_error403Valid key, missing scope
not_found404No such object for this merchant
idempotency_conflict409Key reuse conflict — see below
rate_limited429Slow down; retry after a pause
api_error500Our fault. Safe to query, then retry

Codes

CodeCauseRemedy
parameter_invalidA field failed validation; param names itFix the named field; the message says what’s wrong
idempotency_key_requiredPOST /v1/payments without an Idempotency-Key headerAdd the header — why it’s mandatory
idempotency_key_reusedSame key, different bodyYou have two different requests sharing a key; give each intent its own key
request_in_progressSame key while the first attempt is still executingBack off briefly and retry; you’ll get the original response replayed
reference_in_useThis merchant already has a payment with this referenceFetch the existing payment instead of creating a duplicate
msisdn_invalidNot a Zimbabwean mobile numberNormalise to 2637XXXXXXXX; show the error message to the user verbatim
resource_missingThe id doesn’t exist, or belongs to another merchantCheck the id and which key (test vs live) you’re using — objects don’t cross environments
link_expiredThe payment link’s expires_at has passedCreate a fresh link
link_usedA single-use link was already paid and its payment can’t be resolvedCreate a fresh link; investigate the original payment in the dashboard
refund_amount_invalidRefund exceeds what remains refundableCheck prior partial refunds with GET /v1/refunds/{id}
rate_limitedToo many requestsExponential backoff; respect any Retry-After header
internalSomething failed on our sideQuery the payment before retrying — the charge may have succeeded — then retry with the same idempotency key

The retry rule

For 5xx and network failures on a POST: query first, then retry with the same Idempotency-Key. That order guarantees you never create a duplicate even when our answer was lost on the wire. For 400-class errors, retrying without changing the request will never help.