API reference
UVerify API
Verify Nigerian identities and businesses with one REST call. Every check returns the same response shape, so you handle one result instead of six.
Introduction
Base URL: https://api.uverify.com.ng/v1. Requests and responses are JSON with snake_case fields. Money is in naira (e.g. 100.5) and timestamps are ISO 8601 in UTC.
Each ID check comes in two services with separate prices: the lookup (e.g. POST /identity/nin), and the lookup + face match (POST /identity/nin/face-match), which also compares a selfie with the photo on the record.
New here? Create an account for a free sandbox key, then try calls in the dashboard’s sandbox playground.
SDKs
Official libraries for Node.js, Python and PHP wrap every endpoint with typed responses, retries that never charge twice, one error type with the stable code, and webhook signature checks. The React Native and Flutter packages show the face check (or a verification link) inside your app; your server then runs the face match with the session id.
npm install @uverifyng/node
import { UVerify } from '@uverifyng/node';
const uverify = new UVerify({ apiKey: process.env.UVERIFY_API_KEY });
const v = await uverify.identity.nin({ id_number: '12345678901' });
if (v.status === 'verified') console.log(v.data);Authentication
Send your API key as a bearer token: Authorization: Bearer uvk_test_…. Sandbox keys start uvk_test_, and live keys start uvk_live_. The key decides the environment, so going live means swapping the key and nothing else.
Keep keys on your server. Never put them in a mobile app, browser code or a public repository. If one leaks, revoke it in the dashboard and create another.
Key restrictions (optional, set per key under API keys): limit a key to scopes (identity, business, liveness, kyc, read), to an IP allowlist (addresses or CIDR ranges), and to a requests-per-minute limit. A key with no restrictions can call everything. Refusals are 403 insufficient_scope and 403 ip_not_allowed.
Sandbox
Sandbox keys are free, never touch a real registry, and answer based on the last two characters of id_number:
| …00 | not_found |
| …99 | failed (simulated registry outage) |
| …98 | verified, but face_match is not_matched (face-match endpoints) |
| …97 | verified as a second test person (Tunde Bello), for duplicate face tests |
| anything else | verified (face match: matched) |
Every found person is ADAEZE TEST OKAFOR, born 1990-01-15. Send those for all-true field_matches, or anything else to see mismatches.
Responses & errors
Every response uses the same envelope. A check that ran always returns HTTP 200, so decide on data.status, not the HTTP code.
{
"success": true,
"data": {
"…": "…"
},
"request_id": "req_6f1c…"
}{
"success": false,
"error": {
"code": "validation_error",
"message": "One or more fields are invalid.",
"details": [
"id_number must be the 11-digit BVN"
]
},
"request_id": "req_6f1c…"
}| HTTP | error.code | What to do |
|---|---|---|
| 400 | validation_error | Fix the fields listed in error.details. |
| 401 | invalid_api_key | Missing, wrong or revoked key. |
| 402 | insufficient_balance | Top up your wallet from the dashboard. |
| 403 | live_not_enabled | Live access isn’t approved for this business yet. |
| 403 | ip_not_allowed | The key has an IP allowlist and this request came from another address. |
| 403 | insufficient_scope | The key isn’t allowed to call this endpoint. error.details.required_scope says which scope it needs. |
| 403 | business_suspended | Your account is suspended. Contact UVerify. |
| 404 | not_found | Unknown verification id. |
| 409 | duplicate_reference | This reference was already used. Fetch the earlier result instead. |
| 422 | liveness_not_usable | That liveness session hasn’t passed, was already used, or is over an hour old. Create a new one. |
| 429 | rate_limited | Over the key’s requests-per-minute limit (120 unless you set one). Wait error.details.retry_after_seconds. |
| 503 | check_unavailable | That service is temporarily paused (e.g. registry outage). |
| 500 | internal_error | Retry with the same reference. You won’t be charged twice. |
Quote request_id when you contact support. You can send your own X-Request-Id header to correlate with your logs.
The verification object
- status
- verified: record found, data holds it (charged). not_found: no record (not charged). failed: registry unreachable, retry with a new reference (not charged).
- type / service
- type is the registry lookup (e.g. nin). service is what you bought: nin, or nin_face_match.
- field_matches
- true/false per field you sent (first_name, last_name, date_of_birth); null when not sent or not on the record. Names match any part of the record’s name, ignoring case and punctuation.
- face_match
- null on lookups. On face-match services: status matched · not_matched · unavailable (with reason), score 0–100, liveness "not_checked".
- data
- The normalised record. Only fields the registry returned are present.
- id_number
- Masked, e.g. 222*****678. UVerify never stores raw ID numbers or photos.
- amount_charged
- Naira, after any automatic refund.
Idempotency
Pass your own reference on every check. If a request times out, retry with the same reference: you’ll get 409 duplicate_reference rather than a second charge, and GET /verifications?reference=… returns the original result.
Rate limits
120 requests per minute per API key unless you set another limit (1–1000) on the key in the dashboard. Beyond that you’ll get 429 rate_limited with error.details.retry_after_seconds.
Billing
Live checks are paid from a prepaid NGN wallet, which you can top up online with Paystack from the dashboard. You’re charged only when a record is found. Not-found lookups and registry errors are refunded automatically, and a face-match check whose face match couldn’t run is refunded down to the lookup price. Check your prices with GET /pricing.
Webhooks
Instead of polling, add an HTTPS endpoint in the dashboard under Webhooks, one for sandbox and one for live. UVerify POSTs a signed JSON event when something finishes:
verification.completed: a check finished (verified, not found or failed).liveness.completed: a liveness session passed, used every try, or expired.kyc.completed: a verification link finished, with itsoutcome.document.completed: an ID document check finished, with itsstatusandreasons.aml.match_found: a name you monitor started to match a sanctions list, with the new matches and the screening to review.webhook.test: sent by the dashboard’s test button.
Events never include identity data. data is the verification or liveness object without the record, so fetch GET /verifications/{id} when you need it.
POST https://yourapp.com/webhooks/uverify
UVerify-Signature: t=1790590000,v1=5f8b…e3a1
UVerify-Event: verification.completed
UVerify-Event-Id: evt_4c1e9f0a6f3d2b1c9e770b7c
{
"id": "evt_4c1e9f0a6f3d2b1c9e770b7c",
"type": "verification.completed",
"created_at": "2026-09-27T10:00:02.000Z",
"environment": "live",
"data": { "id": "0b7c3f4e-…", "reference": "loan-8812", "status": "verified", "data": null, … }
}Verify every request. UVerify-Signature is t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" with your endpoint’s signing secret (whsec_…). Reject anything older than five minutes.
import crypto from 'node:crypto';
// Use the raw request body, before JSON parsing.
function verifyUVerify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // older than 5 minutes
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Reply 2xx quickly (within 10 seconds) and do the work afterwards. Anything else is retried after 1m, 5m, 30m, 2h, 6h and 12h, then marked failed. You can retry from the dashboard at any time. A retry resends the same id, so deduplicate on it. Redirects aren’t followed.
Duplicate face detection
Catch one person opening several accounts, or a face already verified as someone else. An owner turns it on in the dashboard under Settings. From then on, each passed liveness session’s face encoding (numbers, not a photo) is kept encrypted and compared with your earlier customers’ faces, sandbox and live apart. Turning it off deletes them. Live checks are billed per face checked (see GET /pricing, service face_dedupe); sandbox is free, and a check your wallet can’t cover is skipped rather than failing the liveness session.
A liveness session gets duplicates (earlier sessions with the same face). A face match that uses that liveness_session_id gets duplicate_check.status:
clear: a new face.returning: seen before with the same ID number (or never verified).other_id: seen before under a different ID with the same name, likely this person’s other ID.flagged: already verified as a different person. Review before you approve.
Each entry in matches has the earlier verification_id, its masked ID, a relation and a similarity (0 to 100). In sandbox, pass the same face string to the simulate call on two sessions, and use an ID ending in 97 for a different person.
Identity checks
BVN lookup
POST/identity/bvn
Look up a BVN and compare the details you collected with the official record. No selfie.
Billed as bvn.
Body
id_numberstringrequired- The 11-digit Bank Verification Number.
first_namestringrequired- Required by the registry. Compared with the record in field_matches.first_name.
last_namestringrequired- Required by the registry. Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
Sending selfie_image here returns 400. Use the face-match endpoint below.
curl -X POST https://api.uverify.com.ng/v1/identity/bvn \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"22212345678","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "bvn",
"type_label": "BVN lookup",
"service": "bvn",
"service_label": "BVN lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "222*****678",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}BVN + face match
POST/identity/bvn/face-match
The BVN lookup plus a selfie compared with the photo on the record. Priced as its own service.
Billed as bvn_face_match.
Body
id_numberstringrequired- The 11-digit Bank Verification Number.
first_namestringrequired- Required by the registry. Compared with the record in field_matches.first_name.
last_namestringrequired- Required by the registry. Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
selfie_imagestring- Base64 JPEG, PNG or WEBP of the person’s face (a data:image/…;base64, prefix is fine), up to 8MB. Matched against the photo on the ID record. Send this or liveness_session_id.
liveness_session_idstring- A passed liveness session (see Liveness). Its captured face is used as the selfie, so the match is against a proven-live person, and face_match.liveness comes back "passed". Single use, within an hour of passing.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
If the face match can’t run (no photo on the record, no face in the selfie), face_match.status is "unavailable" with a reason, and you are refunded down to the plain lookup price.
With a selfie_image, liveness is "not_checked": a photo of a photo would still match. For high-risk decisions, run a liveness session first and send liveness_session_id instead.
With duplicate face detection on and a liveness_session_id, duplicate_check says whether this face was verified before, and as whom. See Duplicate face detection.
curl -X POST https://api.uverify.com.ng/v1/identity/bvn/face-match \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"22212345678","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812","selfie_image":"<base64 jpeg>"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "bvn",
"type_label": "BVN lookup",
"service": "bvn_face_match",
"service_label": "BVN + face match",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "222*****678",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": {
"status": "matched",
"score": 92,
"liveness": "not_checked"
},
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 150,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}NIN lookup
POST/identity/nin
Look up a NIN and compare the details you collected with the official record. No selfie.
Billed as nin.
Body
id_numberstringrequired- The 11-digit National Identification Number.
first_namestring- Compared with the record in field_matches.first_name.
last_namestring- Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
Sending selfie_image here returns 400. Use the face-match endpoint below.
curl -X POST https://api.uverify.com.ng/v1/identity/nin \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"12345678901","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "nin",
"type_label": "NIN lookup",
"service": "nin",
"service_label": "NIN lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "123*****901",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}NIN + face match
POST/identity/nin/face-match
The NIN lookup plus a selfie compared with the photo on the record. Priced as its own service.
Billed as nin_face_match.
Body
id_numberstringrequired- The 11-digit National Identification Number.
first_namestring- Compared with the record in field_matches.first_name.
last_namestring- Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
selfie_imagestring- Base64 JPEG, PNG or WEBP of the person’s face (a data:image/…;base64, prefix is fine), up to 8MB. Matched against the photo on the ID record. Send this or liveness_session_id.
liveness_session_idstring- A passed liveness session (see Liveness). Its captured face is used as the selfie, so the match is against a proven-live person, and face_match.liveness comes back "passed". Single use, within an hour of passing.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
If the face match can’t run (no photo on the record, no face in the selfie), face_match.status is "unavailable" with a reason, and you are refunded down to the plain lookup price.
With a selfie_image, liveness is "not_checked": a photo of a photo would still match. For high-risk decisions, run a liveness session first and send liveness_session_id instead.
With duplicate face detection on and a liveness_session_id, duplicate_check says whether this face was verified before, and as whom. See Duplicate face detection.
curl -X POST https://api.uverify.com.ng/v1/identity/nin/face-match \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"12345678901","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812","selfie_image":"<base64 jpeg>"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "nin",
"type_label": "NIN lookup",
"service": "nin_face_match",
"service_label": "NIN + face match",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "123*****901",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": {
"status": "matched",
"score": 92,
"liveness": "not_checked"
},
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 150,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Driver’s licence lookup
POST/identity/drivers-license
Look up a Driver’s licence and compare the details you collected with the official record. No selfie.
Billed as drivers_license.
Body
id_numberstringrequired- The FRSC licence number, e.g. ABC12345678DE. Spaces are ignored.
first_namestringrequired- Required by the registry. Compared with the record in field_matches.first_name.
last_namestringrequired- Required by the registry. Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
Sending selfie_image here returns 400. Use the face-match endpoint below.
curl -X POST https://api.uverify.com.ng/v1/identity/drivers-license \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"ABC12345678DE","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "drivers_license",
"type_label": "Driver’s licence lookup",
"service": "drivers_license",
"service_label": "Driver’s licence lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "ABC*******8DE",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Driver’s licence + face match
POST/identity/drivers-license/face-match
The Driver’s licence lookup plus a selfie compared with the photo on the record. Priced as its own service.
Billed as drivers_license_face_match.
Body
id_numberstringrequired- The FRSC licence number, e.g. ABC12345678DE. Spaces are ignored.
first_namestringrequired- Required by the registry. Compared with the record in field_matches.first_name.
last_namestringrequired- Required by the registry. Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
selfie_imagestring- Base64 JPEG, PNG or WEBP of the person’s face (a data:image/…;base64, prefix is fine), up to 8MB. Matched against the photo on the ID record. Send this or liveness_session_id.
liveness_session_idstring- A passed liveness session (see Liveness). Its captured face is used as the selfie, so the match is against a proven-live person, and face_match.liveness comes back "passed". Single use, within an hour of passing.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
If the face match can’t run (no photo on the record, no face in the selfie), face_match.status is "unavailable" with a reason, and you are refunded down to the plain lookup price.
With a selfie_image, liveness is "not_checked": a photo of a photo would still match. For high-risk decisions, run a liveness session first and send liveness_session_id instead.
With duplicate face detection on and a liveness_session_id, duplicate_check says whether this face was verified before, and as whom. See Duplicate face detection.
curl -X POST https://api.uverify.com.ng/v1/identity/drivers-license/face-match \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"ABC12345678DE","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812","selfie_image":"<base64 jpeg>"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "drivers_license",
"type_label": "Driver’s licence lookup",
"service": "drivers_license_face_match",
"service_label": "Driver’s licence + face match",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "ABC*******8DE",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": {
"status": "matched",
"score": 92,
"liveness": "not_checked"
},
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 150,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Voter’s card lookup
POST/identity/voters-card
Look up a Voter’s card and compare the details you collected with the official record. No selfie.
Billed as voters_card.
Body
id_numberstringrequired- The INEC voter identification number (VIN).
first_namestringrequired- Required by the registry. Compared with the record in field_matches.first_name.
last_namestringrequired- Required by the registry. Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
Sending selfie_image here returns 400. Use the face-match endpoint below.
curl -X POST https://api.uverify.com.ng/v1/identity/voters-card \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"90F5B1234567891","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "voters_card",
"type_label": "Voter’s card lookup",
"service": "voters_card",
"service_label": "Voter’s card lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "90F*********891",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Voter’s card + face match
POST/identity/voters-card/face-match
The Voter’s card lookup plus a selfie compared with the photo on the record. Priced as its own service.
Billed as voters_card_face_match.
Body
id_numberstringrequired- The INEC voter identification number (VIN).
first_namestringrequired- Required by the registry. Compared with the record in field_matches.first_name.
last_namestringrequired- Required by the registry. Compared with the record in field_matches.last_name.
dobstring- Date of birth, YYYY-MM-DD. Compared with the record in field_matches.date_of_birth.
include_photoboolean- Return the base64 photo from the ID record in data.photo. Never stored by UVerify.
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
selfie_imagestring- Base64 JPEG, PNG or WEBP of the person’s face (a data:image/…;base64, prefix is fine), up to 8MB. Matched against the photo on the ID record. Send this or liveness_session_id.
liveness_session_idstring- A passed liveness session (see Liveness). Its captured face is used as the selfie, so the match is against a proven-live person, and face_match.liveness comes back "passed". Single use, within an hour of passing.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
If the face match can’t run (no photo on the record, no face in the selfie), face_match.status is "unavailable" with a reason, and you are refunded down to the plain lookup price.
With a selfie_image, liveness is "not_checked": a photo of a photo would still match. For high-risk decisions, run a liveness session first and send liveness_session_id instead.
With duplicate face detection on and a liveness_session_id, duplicate_check says whether this face was verified before, and as whom. See Duplicate face detection.
curl -X POST https://api.uverify.com.ng/v1/identity/voters-card/face-match \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"90F5B1234567891","first_name":"Adaeze","last_name":"Okafor","dob":"1990-01-15","reference":"loan-8812","selfie_image":"<base64 jpeg>"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "voters_card",
"type_label": "Voter’s card lookup",
"service": "voters_card_face_match",
"service_label": "Voter’s card + face match",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "90F*********891",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": {
"status": "matched",
"score": 92,
"liveness": "not_checked"
},
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 150,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Tax ID (TIN) lookup
POST/identity/tin
Confirm a Tax Identification Number is valid and registered.
Billed as tin.
Body
id_numberstringrequired- The TIN, e.g. 12345678-0001.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
curl -X POST https://api.uverify.com.ng/v1/identity/tin \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"12345678-0001","reference":"vendor-221"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "tin",
"type_label": "Tax ID (TIN) lookup",
"service": "tin",
"service_label": "Tax ID (TIN) lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "123*******001",
"field_matches": null,
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"id_number": "12345678-0001",
"id_type": "TIN",
"full_name": "ACME LENDING LIMITED"
},
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Liveness
Create a liveness session
POST/liveness/sessions
A hosted camera check (about 20 seconds) that proves a real person is present: the face moves closer (3D depth check), the screen flashes random colours that must reflect off the face, then two random prompts, plus anti-spoof analysis. Send the person to url, then use the session in a face match.
Billed as liveness.
Body
referencestring- Your unique ID for this session (≤100 chars). Generated if omitted.
redirect_urlstring- https URL to send the person to when they finish. We add liveness_session_id, status and reference to it.
url is only returned here. It works on phones and desktops with a camera, and the person gets 3 tries.
Live sessions are charged when created and refunded automatically if not completed within 30 minutes. Sandbox is free.
Flow: create → send the person to url → they return to redirect_url (or poll GET /liveness/sessions/{id}) → if status is "passed", call a /face-match endpoint with liveness_session_id.
Guards against printed photos, photos or videos shown on a screen (flat, so they fail the depth check), pre-recorded videos (they can’t reflect colours chosen seconds earlier), and still images.
Not certified to ISO/IEC 30107-3, and a browser can’t fully rule out injected (virtual-camera) video. For regulated, high-value onboarding, combine it with your other risk checks.
curl -X POST https://api.uverify.com.ng/v1/liveness/sessions \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"reference":"onboard-3381","redirect_url":"https://yourapp.com/kyc/done"}'{
"success": true,
"data": {
"id": "5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30",
"reference": "onboard-3381",
"environment": "live",
"status": "pending",
"live": false,
"score": null,
"reasons": [],
"attempts": 0,
"max_attempts": 3,
"usable_for_face_match": false,
"used_by_verification_id": null,
"duplicates": null,
"redirect_url": "https://yourapp.com/kyc/done",
"amount_charged": 50,
"currency": "NGN",
"expires_at": "2026-09-27T10:30:00.000Z",
"completed_at": null,
"created_at": "2026-09-27T10:00:00.000Z",
"url": "https://verify.elasto.ng/liveness/5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30#<token>"
},
"request_id": "req_6f1c2a…"
}Retrieve a liveness session
GET/liveness/sessions/{id}
The result: pending, passed, failed (every try used) or expired. Poll this if you don’t use redirect_url.
reasons lists why the last try failed, e.g. blink_not_detected or spoof_suspected.
curl https://api.uverify.com.ng/v1/liveness/sessions/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"id": "5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30",
"reference": "onboard-3381",
"environment": "live",
"status": "passed",
"live": true,
"score": 96,
"reasons": [],
"attempts": 1,
"max_attempts": 3,
"usable_for_face_match": true,
"used_by_verification_id": null,
"duplicates": null,
"redirect_url": "https://yourapp.com/kyc/done",
"amount_charged": 50,
"currency": "NGN",
"expires_at": "2026-09-27T10:30:00.000Z",
"completed_at": "2026-09-27T10:01:12.000Z",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Simulate a result (sandbox)
POST/liveness/sessions/{id}/simulate
Sandbox keys only: finish a session as passed or failed without a camera, for automated tests.
Body
outcomestringrequired- "passed", "failed" or "expired" (as if the person never finished; the session is refunded).
facestring- Stands in for a person when testing duplicate face detection: the same string on two sessions is the same face.
curl -X POST https://api.uverify.com.ng/v1/liveness/sessions/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77/simulate \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"outcome":"passed","face":"customer-a"}'{
"success": true,
"data": {
"id": "5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30",
"reference": "onboard-3381",
"environment": "test",
"status": "passed",
"live": true,
"score": 97,
"reasons": [],
"attempts": 1,
"max_attempts": 3,
"usable_for_face_match": true,
"used_by_verification_id": null,
"duplicates": null,
"redirect_url": "https://yourapp.com/kyc/done",
"amount_charged": 0,
"currency": "NGN",
"expires_at": "2026-09-27T10:30:00.000Z",
"completed_at": null,
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}ID documents
Check an ID document
POST/documents/verify
Send a photo of a NIN slip or card, driver’s licence, voter’s card or international passport. UVerify reads it, checks it (type, expiry, passport check digits, visible tampering), looks its number up in the registry, and can match its photo to the customer’s face.
Billed as document.
Body
front_imagestringrequired- Base64 JPEG, PNG or WEBP of the front (the side with the number and photo), up to 8MB. A clear, flat, well-lit photo of the whole document.
back_imagestring- Optional photo of the back.
document_typestring- What you asked for: nin_slip, nin_card, drivers_license, voters_card or passport. A different document is rejected.
liveness_session_idstring- Match the photo on the document to the face that passed this liveness session (the session stays usable for a face match).
selfie_imagestring- Or match it to an uploaded selfie (no liveness proof).
verify_with_registryboolean- Also look the number up (NIN, licence or voter’s card), billed as that lookup. Default true. Passports have no registry lookup.
referencestring- Your unique ID for this check (≤90 chars). The registry lookup gets this reference plus "-registry".
sandbox_outcomestring- Sandbox only: verified, unreadable, expired, tampered, not_in_registry, other_person or face_mismatch. No image is read in sandbox.
status is verified, review (a person should look: possible tampering, one detail differs from the registry, a check couldn’t run) or rejected (unreadable, expired, the wrong document, not in the registry, the number belongs to someone else, or not the same face). reasons lists why.
A document that can’t be read is refunded. Photos are never stored; what was read is kept encrypted.
A document.completed webhook is sent with the result (without the extracted fields).
curl -X POST https://api.uverify.com.ng/v1/documents/verify \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"front_image":"<base64 jpeg>","document_type":"nin_card","liveness_session_id":"5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30","reference":"onboard-3381-id"}'{
"success": true,
"data": {
"id": "7c2e1a90-4b3d-4f6e-9a18-3d5c0b7e2f41",
"reference": "onboard-3381-id",
"environment": "live",
"status": "verified",
"reasons": [],
"document_type": "nin_card",
"expected_type": "nin_card",
"id_number": "123*****901",
"checks": {
"document_type_matches": true,
"expired": false,
"has_photo": true,
"mrz": null,
"tampering_signals": [],
"quality_issues": [],
"registry": {
"status": "verified",
"verification_id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
}
},
"face_match": {
"status": "matched",
"score": 91
}
},
"verification_id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"liveness_session_id": "5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30",
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-29T10:00:00.000Z",
"extracted": {
"id_number": "12345678901",
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"date_of_birth": "1990-01-15",
"gender": "F",
"issue_date": "2021-01-01",
"expiry_date": "2031-01-01",
"nationality": "NGA"
}
},
"request_id": "req_6f1c2a…"
}Get a document check
GET/documents/{id}
The result, including the fields read off the document.
curl https://api.uverify.com.ng/v1/documents/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"id": "7c2e1a90-4b3d-4f6e-9a18-3d5c0b7e2f41",
"reference": "onboard-3381-id",
"environment": "live",
"status": "verified",
"reasons": [],
"document_type": "nin_card",
"expected_type": "nin_card",
"id_number": "123*****901",
"checks": {
"document_type_matches": true,
"expired": false,
"has_photo": true,
"mrz": null,
"tampering_signals": [],
"quality_issues": [],
"registry": {
"status": "verified",
"verification_id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
}
},
"face_match": {
"status": "matched",
"score": 91
}
},
"verification_id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"liveness_session_id": "5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30",
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-29T10:00:00.000Z",
"extracted": {
"id_number": "12345678901",
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"date_of_birth": "1990-01-15",
"gender": "F",
"issue_date": "2021-01-01",
"expiry_date": "2031-01-01",
"nationality": "NGA"
}
},
"request_id": "req_6f1c2a…"
}AML screening
Screen a name
POST/aml/screen
Check a person or organisation against the UN, US OFAC (SDN), UK, EU and Nigeria (NIGSAC) sanctions lists, refreshed daily. Matching allows for spelling, word order, titles and transliteration; a date of birth sharpens it.
Billed as aml_screening.
Body
namestringrequired- Full name as on their ID (at least first and last name for a person), or the organisation’s name.
entity_typestring- "person" (default) or "entity". People are only compared with listed people, organisations with organisations.
date_of_birthstring- YYYY-MM-DD or YYYY. A matching birth year raises the score; a different one lowers it (most false matches drop out).
nationalitystring- Stored with the screening for your records.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
status is clear or potential_match. A match is a lead, not a verdict: compare it with what you know (date of birth, nationality) and record a decision in the dashboard.
score is 0–100; potential matches start at 85. Each match lists its source, programme, listed birth dates and dob_match.
lists_checked shows how fresh each list was. Sandbox screenings use the real lists and are free.
curl -X POST https://api.uverify.com.ng/v1/aml/screen \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Abubakar Shekau","date_of_birth":"1969-01-01","reference":"onboard-3381-aml"}'{
"success": true,
"data": {
"id": "3a9f2c1e-7b4d-4e8a-9c21-5d6e7f8a9b0c",
"reference": "onboard-3381-aml",
"environment": "live",
"status": "potential_match",
"entity_type": "person",
"name": "Abubakar Shekau",
"date_of_birth": "1969-01-01",
"nationality": null,
"matches": [
{
"source": "un",
"source_label": "UN Security Council Consolidated List",
"list_id": "QDi.322",
"kind": "person",
"name": "ABUBAKAR SHEKAU",
"matched_name": "ABUBAKAR SHEKAU",
"score": 100,
"date_of_birth": [
"1969"
],
"dob_match": true,
"nationalities": [
"Nigeria"
],
"programs": [
"Al-Qaida"
],
"listed_on": "2014-06-26"
}
],
"lists_checked": [
{
"source": "un",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 1011
},
{
"source": "ofac",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 19391
},
{
"source": "uk",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 6339
},
{
"source": "eu",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 3651
},
{
"source": "ng",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 69
}
],
"decision": null,
"decision_note": null,
"decided_at": null,
"amount_charged": 50,
"currency": "NGN",
"created_at": "2026-09-29T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Get a screening
GET/aml/screenings/{id}
The screening, its matches and any decision recorded in the dashboard.
curl https://api.uverify.com.ng/v1/aml/screenings/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"id": "3a9f2c1e-7b4d-4e8a-9c21-5d6e7f8a9b0c",
"reference": "onboard-3381-aml",
"environment": "live",
"status": "potential_match",
"entity_type": "person",
"name": "Abubakar Shekau",
"date_of_birth": "1969-01-01",
"nationality": null,
"matches": [
{
"source": "un",
"source_label": "UN Security Council Consolidated List",
"list_id": "QDi.322",
"kind": "person",
"name": "ABUBAKAR SHEKAU",
"matched_name": "ABUBAKAR SHEKAU",
"score": 100,
"date_of_birth": [
"1969"
],
"dob_match": true,
"nationalities": [
"Nigeria"
],
"programs": [
"Al-Qaida"
],
"listed_on": "2014-06-26"
}
],
"lists_checked": [
{
"source": "un",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 1011
},
{
"source": "ofac",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 19391
},
{
"source": "uk",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 6339
},
{
"source": "eu",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 3651
},
{
"source": "ng",
"synced_at": "2026-09-29T02:00:00.000Z",
"entries": 69
}
],
"decision": "cleared",
"decision_note": "Different person: born 1990 in Lagos.",
"decided_at": "2026-09-29T10:20:00.000Z",
"amount_charged": 50,
"currency": "NGN",
"created_at": "2026-09-29T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Monitor a name
POST/aml/monitors
Screen a name now and keep watching it: it’s re-screened after every daily list update, and a new potential-match screening is raised (with an aml.match_found webhook and an email to your owners) when a match appears that wasn’t there before. Or pass monitor: true to POST /aml/screen.
Billed as aml_monitoring.
Body
namestringrequired- Full name, or the organisation’s name.
entity_typestring- "person" (default) or "entity".
date_of_birthstring- YYYY-MM-DD or YYYY.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
The first screening is charged as a normal AML screening. Monitoring is then billed on the 1st for each name watched during the previous month. If your wallet can’t cover it, the name is paused (not watched) until you resume it.
GET /aml/monitors lists them; GET /aml/monitors/{id} fetches one; DELETE /aml/monitors/{id} stops watching.
curl -X POST https://api.uverify.com.ng/v1/aml/monitors \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Adaeze Okafor","date_of_birth":"1990-01-15","reference":"customer-4471"}'{
"success": true,
"data": {
"id": "8d2c…",
"reference": "customer-4471",
"environment": "live",
"status": "active",
"entity_type": "person",
"name": "Adaeze Okafor",
"date_of_birth": "1990-01-15",
"matches_known": 0,
"last_screening_id": "3a9f…",
"last_checked_at": "2026-09-30T10:00:00.000Z",
"verification_id": null,
"created_at": "2026-09-30T10:00:00.000Z",
"stopped_at": null
},
"request_id": "req_6f1c2a…"
}Lists and freshness
GET/aml/lists
Which sanctions lists are screened, how many entries each has, and when each was last updated.
curl https://api.uverify.com.ng/v1/aml/lists \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": [
{
"source": "un",
"label": "UN Security Council Consolidated List",
"publisher": "United Nations",
"url": "https://main.un.org/securitycouncil/en/content/un-sc-consolidated-list",
"status": "ok",
"entries": 1011,
"synced_at": "2026-09-29T02:00:00.000Z"
}
],
"request_id": "req_6f1c2a…"
}Verification links
Create a verification link
POST/kyc/requests
A hosted page where your customer picks an ID, enters it, and does the liveness check; UVerify then face-matches them. You read one outcome. No UI to build (also available with no code from the dashboard).
Body
referencestring- Your unique ID for this customer check (≤100 chars). Generated if omitted.
id_typesstring[]- IDs the customer may use: bvn, nin, drivers_license, voters_card. Default ["bvn", "nin"].
customer_namestring- Greets the customer ("Hi Ada") and pre-fills their name.
customer_emailstring- Stored with the request for your records.
redirect_urlstring- https URL to send the customer to when they finish. We add kyc_request_id, status, outcome and reference.
require_documentboolean- Also ask for a photo of their ID after the face check (NIN slip or card, licence, voter’s card or passport). It’s read, checked, and its photo matched to their face; the link’s document field has the result. Adds the ID document check price.
url is only returned here. Links work for 7 days and can be completed once.
Billed as a liveness session plus the face-match check, when the customer does them. An unfinished liveness step is refunded.
If the ID isn’t found, the customer can correct it up to 3 times without redoing the face check.
curl -X POST https://api.uverify.com.ng/v1/kyc/requests \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"reference":"customer-4471","customer_name":"Ada Okafor","redirect_url":"https://yourapp.com/kyc/done"}'{
"success": true,
"data": {
"id": "9a2e7c41-5b3d-4f8e-a6c0-1d2b3e4f5a6b",
"reference": "customer-4471",
"environment": "live",
"status": "pending",
"outcome": null,
"customer_name": "Ada Okafor",
"customer_email": null,
"id_types": [
"bvn",
"nin"
],
"id_type": null,
"id_number": null,
"attempts": 0,
"verification": null,
"liveness": null,
"require_document": false,
"document": null,
"amount_charged": 0,
"currency": "NGN",
"redirect_url": "https://yourapp.com/kyc/done",
"expires_at": "2026-10-04T10:00:00.000Z",
"completed_at": null,
"created_at": "2026-09-27T10:00:00.000Z",
"url": "https://verify.elasto.ng/kyc/9a2e7c41-5b3d-4f8e-a6c0-1d2b3e4f5a6b#<token>"
},
"request_id": "req_6f1c2a…"
}Retrieve a verification link
GET/kyc/requests/{id}
Status and outcome. outcome is verified, face_mismatch, face_unavailable (no usable ID photo), not_found, liveness_failed or error; links with require_document can also end document_rejected (the ID photo failed its checks) or document_mismatch (it names someone else). A kyc.completed webhook is sent when it finishes.
verification never includes the identity record here; fetch GET /verifications/{id} for it.
curl https://api.uverify.com.ng/v1/kyc/requests/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"id": "9a2e7c41-5b3d-4f8e-a6c0-1d2b3e4f5a6b",
"reference": "customer-4471",
"environment": "live",
"status": "completed",
"outcome": "verified",
"customer_name": "Ada Okafor",
"customer_email": null,
"id_types": [
"bvn",
"nin"
],
"id_type": "bvn",
"id_number": "222*****678",
"attempts": 1,
"verification": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"status": "verified",
"face_match": {
"status": "matched",
"score": 91,
"liveness": "passed"
},
"data": null
},
"liveness": {
"id": "5f1d9c2a-3b7e-4c0d-8a6f-2e9b1c4d7a30",
"status": "passed",
"score": 97,
"attempts": 1
},
"require_document": false,
"document": null,
"amount_charged": 200,
"currency": "NGN",
"redirect_url": "https://yourapp.com/kyc/done",
"expires_at": "2026-10-04T10:00:00.000Z",
"completed_at": "2026-09-27T10:03:12.000Z",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Business checks
CAC business lookup
POST/business/cac
Company status, registered address, directors and beneficial owners from the Corporate Affairs Commission.
Billed as cac.
Body
id_numberstringrequired- The CAC number starting with RC, BN or IT, e.g. RC123456. Spaces are ignored.
business_typestring- Optional hint, e.g. "PRIVATE LIMITED".
aml_screeningboolean- When the record is found, screen it against the sanctions lists (for CAC: the company and each director). Billed per name at the AML screening price; the result is in aml_screening.
aml_monitoringboolean- Screen as above and keep watching those names: re-screened after every list update, with an aml.match_found webhook when one starts to match. Billed monthly per name.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
curl -X POST https://api.uverify.com.ng/v1/business/cac \
-H "Authorization: Bearer $UVERIFY_KEY" \
-H "Content-Type: application/json" \
-d '{"id_number":"RC123456","reference":"kyb-77"}'{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "cac",
"type_label": "CAC business lookup",
"service": "cac",
"service_label": "CAC business lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "RC1**456",
"field_matches": null,
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"legal_name": "ACME LENDING LIMITED",
"registration_number": "RC123456",
"company_type": "PRIVATE_COMPANY_LIMITED_BY_SHARES",
"status": "ACTIVE",
"registration_date": "2019-03-14",
"address": "12 Marina, Lagos",
"directors": [
{
"name": "ADA OKAFOR",
"gender": "FEMALE",
"nationality": "NIGERIAN",
"occupation": "DIRECTOR"
}
],
"beneficial_owners": [
{
"name": "ADA OKAFOR",
"shareholdings": "100%"
}
]
},
"amount_charged": 200,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Verifications
List verifications
GET/verifications
Your checks for the key’s environment, newest first. Lists never include identity data.
Query
referencestring- Exact reference. Useful after a timeout to see whether a check went through.
typestring- bvn, nin, drivers_license, voters_card, tin or cac.
servicestring- A priced service, e.g. nin_face_match.
statusstring- verified, not_found or failed.
pageinteger- Default 1.
per_pageinteger- Default 20, max 100.
curl https://api.uverify.com.ng/v1/verifications \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"items": [
{
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "bvn",
"type_label": "BVN lookup",
"service": "bvn",
"service_label": "BVN lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "222*****678",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": null,
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1
}
},
"request_id": "req_6f1c2a…"
}Retrieve a verification
GET/verifications/{id}
One check, including the record data.
curl https://api.uverify.com.ng/v1/verifications/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"id": "0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77",
"reference": "loan-8812",
"type": "bvn",
"type_label": "BVN lookup",
"service": "bvn",
"service_label": "BVN lookup",
"environment": "live",
"status": "verified",
"message": "ID found and verified.",
"id_number": "222*****678",
"field_matches": {
"first_name": true,
"last_name": true,
"date_of_birth": true
},
"face_match": null,
"duplicate_check": null,
"aml_screening": null,
"data": {
"first_name": "ADAEZE",
"middle_name": "TEST",
"last_name": "OKAFOR",
"full_name": "ADAEZE TEST OKAFOR",
"date_of_birth": "15-Jan-1990",
"gender": "Female",
"phone_number": "08000000000",
"state_of_origin": "Anambra",
"nationality": "Nigerian"
},
"amount_charged": 100,
"currency": "NGN",
"created_at": "2026-09-27T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Account
Wallet balance
GET/balance
Your prepaid NGN wallet balance.
curl https://api.uverify.com.ng/v1/balance \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": {
"balance": 48250,
"currency": "NGN"
},
"request_id": "req_6f1c2a…"
}Your prices
GET/pricing
The price you pay per service. custom_price is set when you have a negotiated rate.
curl https://api.uverify.com.ng/v1/pricing \
-H "Authorization: Bearer $UVERIFY_KEY"{
"success": true,
"data": [
{
"check_type": "nin",
"label": "NIN lookup",
"default_price": 100,
"custom_price": null,
"price": 100,
"currency": "NGN",
"is_enabled": true
},
{
"check_type": "nin_face_match",
"label": "NIN + face match",
"default_price": 150,
"custom_price": 130,
"price": 130,
"currency": "NGN",
"is_enabled": true
}
],
"request_id": "req_6f1c2a…"
}Questions? hello@elasto.ng