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.

Install and first call
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:

…00not_found
…99failed (simulated registry outage)
…98verified, but face_match is not_matched (face-match endpoints)
…97verified as a second test person (Tunde Bello), for duplicate face tests
anything elseverified (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
{
  "success": true,
  "data": {
    "…": "…"
  },
  "request_id": "req_6f1c…"
}
Error
{
  "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…"
}
HTTPerror.codeWhat to do
400validation_errorFix the fields listed in error.details.
401invalid_api_keyMissing, wrong or revoked key.
402insufficient_balanceTop up your wallet from the dashboard.
403live_not_enabledLive access isn’t approved for this business yet.
403ip_not_allowedThe key has an IP allowlist and this request came from another address.
403insufficient_scopeThe key isn’t allowed to call this endpoint. error.details.required_scope says which scope it needs.
403business_suspendedYour account is suspended. Contact UVerify.
404not_foundUnknown verification id.
409duplicate_referenceThis reference was already used. Fetch the earlier result instead.
422liveness_not_usableThat liveness session hasn’t passed, was already used, or is over an hour old. Create a new one.
429rate_limitedOver the key’s requests-per-minute limit (120 unless you set one). Wait error.details.retry_after_seconds.
503check_unavailableThat service is temporarily paused (e.g. registry outage).
500internal_errorRetry 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 its outcome.
  • document.completed: an ID document check finished, with its status and reasons.
  • 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"}'
Response · 200
{
  "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>"}'
Response · 200
{
  "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"}'
Response · 200
{
  "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>"}'
Response · 200
{
  "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"}'
Response · 200
{
  "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>"}'
Response · 200
{
  "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"}'
Response · 200
{
  "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>"}'
Response · 200
{
  "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"}'
Response · 200
{
  "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"}'
Response · 200
{
  "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"
Response · 200
{
  "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"}'
Response · 200
{
  "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"}'
Response · 200
{
  "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"
Response · 200
{
  "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"}'
Response · 200
{
  "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"
Response · 200
{
  "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"}'
Response · 200
{
  "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"
Response · 200
{
  "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"}'
Response · 200
{
  "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"
Response · 200
{
  "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"}'
Response · 200
{
  "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"
Response · 200
{
  "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"
Response · 200
{
  "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"
Response · 200
{
  "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"
Response · 200
{
  "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