Conventions
This page collects the cross-cutting rules every endpoint follows. Skim it once so the rest of the docs feel familiar.
Content types
Section titled “Content types”- Verification endpoints accept and return
application/json. POST /loginacceptsapplication/x-www-form-urlencoded(OAuth2 password flow). Every other endpoint is JSON.- Responses always include a
Content-Type: application/json; charset=utf-8header.
Status codes
Section titled “Status codes”| Code | Meaning |
|---|---|
200 | The request succeeded. The body contains a data envelope for verification calls, or a top-level resource for other calls. |
400 | Validation failed — your request body is missing a required field or contains an invalid value. The detail field names the problem. |
401 | Token is missing, expired, or invalid. Re-authenticate. |
402 | Insufficient units. Top up your wallet before retrying. |
403 | Token is valid, but the user lacks permission for this action. |
404 | The referenced resource does not exist. Most common on /verify-nin/requery with an unknown reference_id. |
422 | Pydantic validation error — typically a malformed payload (wrong type, missing field). |
429 | Rate-limited — slow down and retry with backoff. |
5xx | RandaVerify is having a bad day. Retry with exponential backoff; if it persists, contact support with your reference_id. |
The error body shape is consistent:
{ "detail": "Insufficient units in organisation wallet" }Field-name casing
Section titled “Field-name casing”Verification responses use camelCase for NIMC-sourced fields (e.g. dateOfBirth, firstname, residenceAddressLine1) but RandaVerify normalises them to a consistent snake-friendly schema before returning. You always receive:
| Field | Source aliases |
|---|---|
nin | nin, NIN, identityNumber |
fname | firstname, firstName, fname |
mname | middlename, middleName, mname |
lname | surname, lastName, lname |
dob | dateOfBirth, birthdate, dob |
phone | telephoneno, mobile, phone, phone1 |
residenceAdress | residence_AdressLine1, address (note original NIMC mis-spelling preserved) |
residenceTown | town, city |
residenceLga | residence_lga, lga |
residenceState | state |
stateOfOrigin | self_origin_state, birthState |
lgaOfOrigin | self_origin_lga, birthLga |
image | photo, base64 JPEG |
This way you do not need to know which key NIMC happened to use — RandaVerify returns the same shape every time.
Reference IDs
Section titled “Reference IDs”Every verification produces a reference_id of the form REF-XXXXXXXXXXXX. Persist it. You can:
- Look the record up again later with
/verify-nin/requery(no unit charged). - Quote it to support when reporting an issue.
- Use it as your idempotency key for retry-safe persistence on your side.
Phone numbers & dates
Section titled “Phone numbers & dates”- Phone numbers are Nigerian 11-digit format beginning with
0(e.g.08012345678). - Dates of birth are
dd-mm-yyyy(e.g.03-09-1999) on the demography request side. Thedobreturned in responses uses the same format.
Reasons
Section titled “Reasons”NIMC validates every verification against a fixed list of allowed reasons. Always fetch the current list from GET /nimc-reasons and pass the key (not the human label) in your request:
curl https://api.randaverify.com/v1/nimc-reasons -H "Authorization: Bearer $TOKEN"{ "data": [ { "key": "corporate", "label": "Corporate", "description": "..." }, { "key": "nyscCheck", "label": "NYSC Check", "description": "..." }, { "key": "telecommunicationSimReg", "label": "Telecom SIM Reg", "description": "..." } ]}Wallet semantics
Section titled “Wallet semantics”- Every successful verification debits one unit from your org wallet.
- A failed verification does not debit your wallet (you only pay for results).
- A re-query does not debit your wallet (idempotent retrieval).
- You can check your current balance via
GET /v1/me→organisation(the unit count is available in admin contexts; for the standard officer flow, see your wallet via the portal).
Retries & idempotency
Section titled “Retries & idempotency”The single most important rule: do not blindly retry a POST /v1/verify-nin* request on transient errors. Verification POSTs are not idempotent — every successful call mints a new transaction and debits a unit. A naive retry on a network blip will charge your wallet twice.
The right pattern:
| Outcome | Did a unit get charged? | What to do |
|---|---|---|
HTTP 2xx received with a reference_id | Yes | You’re done. Persist the reference_id. To “retry” (e.g. user pressed back and re-submitted), use POST /v1/verify-nin/requery with the same reference — it’s free and idempotent. |
HTTP 4xx (validation, 402, 403, 404) | No | The request never reached NIMC. Fix the client input and retry safely. |
HTTP 5xx or network failure / timeout | Maybe — the request may or may not have completed upstream | Do not retry blindly. Wait 30 seconds, then call /v1/verify-nin/requery with the reference you would have used. If your client never saw a reference, you can also list your recent transactions in the admin portal to confirm whether a charge landed. |
Re-queries (POST /v1/verify-nin/requery) and reads (GET /v1/me, GET /v1/nimc-reasons) are idempotent and safe to retry freely.
Calling from your backend (CORS)
Section titled “Calling from your backend (CORS)”The RandaVerify API is meant to be called server-to-server, not directly from a customer’s browser. The CORS allow-list on api.randaverify.com is locked to the official RandaVerify domains; any Origin header from partner.com.ng (or any other domain) will be rejected by the preflight.
In practice:
- ✅ Your backend code (Node, Python, Go, PHP, whatever) calls our API directly. Bearer tokens stay server-side.
- ❌ Your frontend JavaScript should not call
api.randaverify.comdirectly. CORS will block it, and even if it didn’t, you would be leaking your bearer token to every visitor’s browser. - The right pattern: your frontend calls your backend, your backend calls ours. This also lets you cache, rate-limit, log, and apply your own business rules between the two.
If your customer-facing UI needs to surface verification results, fetch them on your backend, store the reference_id, and re-query from your backend later if the user revisits.