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.
The ones you will actually meet
Section titled “The ones you will actually meet”| Code | Status | Retry? | What to do |
|---|---|---|---|
unauthenticated | 401 | No | Check the key. Revoked, expired and wrong all look the same on purpose. |
insufficient_scope | 403 | No | detail.required_scope names what was missing. |
signature_invalid | 401 | No | Re-read the signed string. Usually the body hash or the clock. |
nin_invalid | 400 | No | A NIN is exactly 11 digits. Validate before sending. |
nin_not_found | 404 | No | The registry holds no such record. Not an error on your side. |
verification_declined | 422 | No | The registry refused. A unit may still have been charged. |
insufficient_units | 402 | No | Top up. |
rate_limited | 429 | Yes | Back off. See the X-RateLimit-* headers. |
idempotency_in_flight | 409 | Yes | Your first attempt is still running. Retry shortly, unchanged. |
idempotency_key_reused | 422 | No | Same key, different body. Use a new key. |
upstream_unavailable | 503 | Yes | The registry is not answering. No unit charged. |
upstream_timeout | 504 | Yes | The registry did not answer in time. No unit charged. |
internal_error | 500 | Yes | Ours. Quote request_id when you tell us. |
Request ids
Section titled “Request ids”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.