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). The same value is also returned as residenceAddress and address, so any of the three names works. |
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.
NIMC currently returns only the residence address line. residenceTown, residenceLga and residenceState are usually empty strings, and stateOfOrigin and lgaOfOrigin are empty when NIMC has no value on record. Display them only when they are non-empty.
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 lookup NIMC completes debits one unit from your org wallet. That includes a match, and a no-match answer from NIMC:
Invalid NIN,Suspended NIN,Record not found, or a NIN restricted by its holder. - Errors do not debit your wallet: timeouts, NIMC outages or “try again later”, and requests we reject before calling NIMC (for example an invalid
reason). - 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 400 or 404 carrying a NIMC answer: Invalid NIN, Suspended NIN, Record not found, or a restricted NIN | Yes, 1 unit | NIMC completed the lookup and answered. Do not retry the same input: the answer will not change and each attempt is charged. Correct the NIN or details first. |
Any other HTTP 4xx (validation, invalid reason, 402, 403) | 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.