Skip to content

Errors

Every refusal from v2 looks the same:

{
"error": {
"code": "insufficient_units",
"message": "Not enough units to perform this verification.",
"retryable": false,
"request_id": "req_75cf8b7cba3959a75a37746f",
"catalogue": "/v2/errors"
}
}

retryable tells you whether trying again could possibly work. Retrying a nin_invalid forever will never succeed; giving up on an upstream_timeout throws away a verification that would have.

GET /v2/errors returns the live catalogue, so you can enumerate the codes rather than discover them in production.

CodeStatusRetry?What to do
unauthenticated401NoCheck the key. Revoked, expired and wrong all look the same on purpose.
insufficient_scope403Nodetail.required_scope names what was missing.
signature_invalid401NoRe-read the signed string. Usually the body hash or the clock.
nin_invalid400NoA NIN is exactly 11 digits. Validate before sending.
nin_not_found404NoThe registry holds no such record. Not an error on your side.
verification_declined422NoThe registry refused. A unit may still have been charged.
insufficient_units402NoTop up.
rate_limited429YesBack off. See the X-RateLimit-* headers.
idempotency_in_flight409YesYour first attempt is still running. Retry shortly, unchanged.
idempotency_key_reused422NoSame key, different body. Use a new key.
upstream_unavailable503YesThe registry is not answering. No unit charged.
upstream_timeout504YesThe registry did not answer in time. No unit charged.
internal_error500YesOurs. Quote request_id when you tell us.

Every response carries X-Request-Id, and errors repeat it in the body. Log it. It is the one thing that lets us find your exact call, and GET /v2/requests shows your own recent calls with their outcomes and latencies.