API reference
ByteID 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.byteid.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 { ByteID } from '@uverifyng/node';
const byteid = new ByteID({ apiKey: process.env.BYTEID_API_KEY });
const v = await byteid.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 ByteID. |
| 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. ByteID 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. ByteID 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.address.completed: an address link finished (with itsresultandlocation.distance_m) or expired unused.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/byteid
ByteID-Signature: t=1790590000,v1=5f8b…e3a1
ByteID-Event: verification.completed
ByteID-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. ByteID-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 verifyByteID(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.
Address checks
Three ways to check where a customer lives, each priced per check (see GET /pricing):
POST /address/verify(serviceaddress): the address is found on the map and checked against the LGA and state given. Instant.statusisverified,partialorfailed, with ascore, each check and thereasons.POST /address/links(serviceaddress_location): a hosted page for your customer to open at home and share their phone’s location once. We measure how far it is from the address and sendaddress.completed. Charged when created, refunded if unused within 72 hours. Addrequire_document: trueto also ask for a proof of address.POST /address/documents(serviceaddress_document): a photo of a recent utility bill, bank statement or tenancy agreement, checked for the customer’s name, the address, its date and signs of editing. Refunded when it can’t be read.
Results found on OpenStreetMap carry attribution (“© OpenStreetMap contributors”): show it wherever you display the result. A phone’s location can be faked with special apps, so a location result is strong evidence, not proof.
In sandbox nothing is looked up: pass sandbox_outcome to choose the result (verified, partial, not_found or state_mismatch for a check; verified, partial or failed for a link), or finish a link with POST /address/links/{id}/simulate.
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_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 ByteID.
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.byteid.ng/v1/identity/bvn \
-H "Authorization: Bearer $BYTEID_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_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 ByteID.
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.byteid.ng/v1/identity/bvn/face-match \
-H "Authorization: Bearer $BYTEID_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 ByteID.
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.byteid.ng/v1/identity/nin \
-H "Authorization: Bearer $BYTEID_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 ByteID.
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.byteid.ng/v1/identity/nin/face-match \
-H "Authorization: Bearer $BYTEID_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 ByteID.
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.byteid.ng/v1/identity/drivers-license \
-H "Authorization: Bearer $BYTEID_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 ByteID.
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.byteid.ng/v1/identity/drivers-license/face-match \
-H "Authorization: Bearer $BYTEID_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 ByteID.
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.byteid.ng/v1/identity/voters-card \
-H "Authorization: Bearer $BYTEID_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 ByteID.
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.byteid.ng/v1/identity/voters-card/face-match \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/identity/tin \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/liveness/sessions \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/liveness/sessions/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/liveness/sessions/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77/simulate \
-H "Authorization: Bearer $BYTEID_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. ByteID 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.byteid.ng/v1/documents/verify \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/documents/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/aml/screen \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/aml/screenings/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/aml/monitors \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/aml/lists \
-H "Authorization: Bearer $BYTEID_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…"
}Address checks
Check an address
POST/address/verify
Is this a real address, in the LGA and state the customer gave? The address is looked up on the map (Nigeria only) and scored on how precisely it was found and whether the state and LGA agree. Instant.
Billed as address.
Body
addressstringrequired- House number and street, with the area or town, e.g. "12 Admiralty Way, Lekki Phase 1".
statestringrequired- A Nigerian state or FCT, e.g. "Lagos" (also accepts "Lagos State", "Abuja").
lgastring- Local government area, e.g. "Eti-Osa". Optional, but it sharpens the check; a town or district name is accepted too.
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
sandbox_outcomestring- Test keys only: verified (default), partial, not_found or state_mismatch. Nothing is looked up in the sandbox.
status is verified (score 75+), partial (45–74) or failed. A different state always fails. reasons says what held it back: address_not_found, state_mismatch, lga_mismatch, partial_match, only_area_found, only_state_found.
location.source says which map found it (openstreetmap or google). OpenStreetMap results carry location.attribution; show it wherever you display the result.
checks.address_found.precision is how precisely the address was pinned: building, street, area (town or district only) or region (state only).
This confirms the address exists where the customer says; it doesn’t prove they live there. For regulated physical verification, pair it with a proof of address.
Charged per check, also when nothing is found (the lookup ran). Not charged when the map service is unavailable (503 check_unavailable).
curl -X POST https://api.byteid.ng/v1/address/verify \
-H "Authorization: Bearer $BYTEID_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"12 Admiralty Way, Lekki Phase 1","lga":"Eti-Osa","state":"Lagos","reference":"customer-4471-addr"}'{
"success": true,
"data": {
"id": "5e1b…",
"reference": "customer-4471-addr",
"environment": "live",
"status": "verified",
"score": 100,
"address": {
"address": "12 Admiralty Way, Lekki Phase 1",
"lga": "Eti-Osa",
"state": "Lagos"
},
"checks": {
"address_found": {
"result": "pass",
"precision": "building"
},
"state_match": {
"result": "pass",
"found": "Lagos"
},
"lga_match": {
"result": "pass",
"found": "Eti-Osa"
}
},
"reasons": [],
"location": {
"formatted_address": "12, Admiralty Way, Lekki Phase 1, Eti-Osa, Lagos State, Nigeria",
"latitude": 6.4474,
"longitude": 3.4723,
"precision": "building",
"partial_match": false,
"source": "openstreetmap",
"attribution": "© OpenStreetMap contributors"
},
"amount_charged": 150,
"currency": "NGN",
"created_at": "2026-10-09T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Get an address check
GET/address/checks/{id}
One address check and its result. GET /address/checks lists them (filter with ?status=verified|partial|failed).
curl https://api.byteid.ng/v1/address/checks/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_KEY"{
"success": true,
"data": {
"id": "5e1b…",
"reference": "customer-4471-addr",
"environment": "live",
"status": "verified",
"score": 100,
"address": {
"address": "12 Admiralty Way, Lekki Phase 1",
"lga": "Eti-Osa",
"state": "Lagos"
},
"checks": {
"address_found": {
"result": "pass",
"precision": "building"
},
"state_match": {
"result": "pass",
"found": "Lagos"
},
"lga_match": {
"result": "pass",
"found": "Eti-Osa"
}
},
"reasons": [],
"location": {
"formatted_address": "12, Admiralty Way, Lekki Phase 1, Eti-Osa, Lagos State, Nigeria",
"latitude": 6.4474,
"longitude": 3.4723,
"precision": "building",
"partial_match": false,
"source": "openstreetmap",
"attribution": "© OpenStreetMap contributors"
},
"amount_charged": 150,
"currency": "NGN",
"created_at": "2026-10-09T10:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Create an address link (phone location)
POST/address/links
A hosted page your customer opens on their phone while at the address. They share their location once, and we measure how far the phone is from where the address is on the map. Send them the url (by SMS, WhatsApp or email); read the result with GET /address/links/{id} or the address.completed webhook.
Billed as address_location.
Body
addressstringrequired- House number and street, with the area or town. It must be findable on the map: if not, you get 422 and no link (not charged).
statestringrequired- A Nigerian state or FCT.
lgastring- Local government area. Optional, sharpens the address check.
customer_namestring- Greets the customer on the page ("Hi Ada").
redirect_urlstring- https:// URL the customer is sent to when done. We append address_link_id, status (verified, partial, failed or expired) and reference.
require_documentboolean- After their location, the customer also photographs a proof of address (bill, statement, tenancy). Checked and charged as its own proof-of-address check; the link’s result is then the weakest of the three. A customer who shares their location but never uploads it completes as document missing (partial).
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
sandbox_outcomestring- Test keys only: the result sharing a location gives (verified by default, partial or failed). Or finish it with POST /address/links/{id}/simulate.
The link works for 72 hours. It is charged when created and refunded if it expires unused.
How near counts as "at the address" depends on how precisely the map found it: within 150 m of a building (partial up to 500 m), 300 m of a street (1 km), 2 km of an area (5 km), plus the phone’s GPS accuracy up to 100 m.
When the phone is too far from the map’s pin (the map may only know the area, or not the street at all), we also look up where the phone is: its street named in the address counts as at the address (location.area_match: "street"); its area or town as at it when the map only knew the area, otherwise as near it ("area"); only the same LGA as near it ("lga").
The customer gets 5 tries. Readings that are too vague (worse than ±500 m) or old ask them to try again; a reading far from the address asks them to try again from there, and the 5th decides.
A browser can’t tell a real location from a fake-location app, so treat a verified result as strong evidence, not proof.
curl -X POST https://api.byteid.ng/v1/address/links \
-H "Authorization: Bearer $BYTEID_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"12 Admiralty Way, Lekki Phase 1","lga":"Eti-Osa","state":"Lagos","customer_name":"Adaeze Okafor","redirect_url":"https://yourapp.com/address-done","reference":"customer-4471-home"}'{
"success": true,
"data": {
"id": "8a3e…",
"object": "address_link",
"reference": "customer-4471-home",
"environment": "live",
"status": "pending",
"result": null,
"address": {
"address": "12 Admiralty Way, Lekki Phase 1",
"lga": "Eti-Osa",
"state": "Lagos"
},
"customer_name": "Adaeze Okafor",
"address_check": {
"status": "verified",
"score": 85,
"checks": {
"address_found": {
"result": "pass",
"precision": "street"
},
"state_match": {
"result": "pass",
"found": "Lagos"
},
"lga_match": {
"result": "pass",
"found": "Eti Osa"
}
}
},
"place": {
"formatted_address": "Admiralty Way, Lekki Phase 1, Eti Osa, Lagos, Nigeria",
"latitude": 6.4474,
"longitude": 3.4723,
"precision": "street",
"source": "openstreetmap",
"attribution": "© OpenStreetMap contributors"
},
"location": null,
"reasons": [],
"attempts": 0,
"redirect_url": "https://yourapp.com/address-done",
"expires_at": "2026-10-13T09:00:00.000Z",
"completed_at": null,
"amount_charged": 250,
"currency": "NGN",
"created_at": "2026-10-10T09:00:00.000Z",
"url": "https://byteid.ng/address/8a3e…#k3Jx…"
},
"request_id": "req_6f1c2a…"
}Get an address link
GET/address/links/{id}
The link and, once the customer has shared their location, the result: result is verified, partial or failed, with location.distance_m from the address. GET /address/links lists them (?status=pending|completed|expired).
status is pending (waiting for the customer), completed or expired. result is the weaker of the address check and the location check; reasons says why (near_address, far_from_address, location_too_vague, same_street, same_area, same_lga, plus the address check’s reasons).
curl https://api.byteid.ng/v1/address/links/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_KEY"{
"success": true,
"data": {
"id": "8a3e…",
"object": "address_link",
"reference": "customer-4471-home",
"environment": "live",
"status": "completed",
"result": "verified",
"address": {
"address": "12 Admiralty Way, Lekki Phase 1",
"lga": "Eti-Osa",
"state": "Lagos"
},
"customer_name": "Adaeze Okafor",
"address_check": {
"status": "verified",
"score": 85,
"checks": {
"address_found": {
"result": "pass",
"precision": "street"
},
"state_match": {
"result": "pass",
"found": "Lagos"
},
"lga_match": {
"result": "pass",
"found": "Eti Osa"
}
}
},
"place": {
"formatted_address": "Admiralty Way, Lekki Phase 1, Eti Osa, Lagos, Nigeria",
"latitude": 6.4474,
"longitude": 3.4723,
"precision": "street",
"source": "openstreetmap",
"attribution": "© OpenStreetMap contributors"
},
"location": {
"distance_m": 84,
"accuracy_m": 12,
"area_match": null,
"latitude": 6.4481,
"longitude": 3.4726,
"captured_at": "2026-10-10T09:14:03.000Z"
},
"reasons": [],
"attempts": 1,
"redirect_url": "https://yourapp.com/address-done",
"expires_at": "2026-10-13T09:00:00.000Z",
"completed_at": "2026-10-10T09:14:04.000Z",
"amount_charged": 250,
"currency": "NGN",
"created_at": "2026-10-10T09:00:00.000Z"
},
"request_id": "req_6f1c2a…"
}Check a proof of address
POST/address/documents
A photo of a recent utility bill, bank statement or tenancy agreement, read and checked: is it in the customer’s name, for this address, recent, and free of signs of editing? Instant.
Billed as address_document.
Body
front_imagestringrequired- A photo or screenshot of the document: base64 JPEG, PNG or WEBP, up to 8MB. PDFs: send a screenshot of the page.
back_imagestring- A second page, if the name or address is on it.
addressstringrequired- The address it should show.
statestringrequired- A Nigerian state or FCT.
lgastring- Local government area.
namestring- The customer’s full name, compared with the name on the document.
max_age_daysinteger- How recent it must be, in days. Default 92 (about three months).
referencestring- Your unique ID for this check (≤100 chars; letters, numbers, . _ : -). Reusing it returns 409 duplicate_reference instead of charging twice. Generated if omitted.
sandbox_outcomestring- Test keys only: verified (default), address_mismatch, name_mismatch, too_old, tampered or unreadable. Nothing is read in the sandbox.
status is verified, partial (part of the address or one name matched, too old, or undated), failed (someone else’s name, a different address, or signs of editing) or unreadable (refunded: ask for a clearer photo).
checks has each part: name_match, address_match (with the share of the address found, and whether the state and LGA are on it), recency (issue_date, age_days) and tampering_signals. extracted is what was read: issuer, name, address, issue_date.
Images are read and not kept. GET /address/documents/{id} fetches one; GET /address/documents lists them.
curl -X POST https://api.byteid.ng/v1/address/documents \
-H "Authorization: Bearer $BYTEID_KEY" \
-H "Content-Type: application/json" \
-d '{"front_image":"<base64>","address":"12 Admiralty Way, Lekki Phase 1","lga":"Eti-Osa","state":"Lagos","name":"Adaeze Okafor","reference":"customer-4471-bill"}'{
"success": true,
"data": {
"id": "0c7d…",
"object": "address_document",
"reference": "customer-4471-bill",
"environment": "live",
"status": "verified",
"reasons": [],
"address": {
"address": "12 Admiralty Way, Lekki Phase 1",
"lga": "Eti-Osa",
"state": "Lagos"
},
"name": "Adaeze Okafor",
"checks": {
"document_type": "utility_bill",
"name_match": {
"result": "pass"
},
"address_match": {
"result": "pass",
"score": 1,
"state_on_document": true,
"lga_on_document": true
},
"recency": {
"result": "pass",
"issue_date": "2026-09-15",
"age_days": 25,
"max_age_days": 92
},
"tampering_signals": []
},
"extracted": {
"document_type": "utility_bill",
"issuer": "Eko Electricity Distribution",
"name": "MRS ADAEZE OKAFOR",
"address": "12, Admiralty Way, Lekki Phase 1, Lagos",
"issue_date": "2026-09-15"
},
"address_link_id": null,
"amount_charged": 200,
"currency": "NGN",
"created_at": "2026-10-10T09: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; ByteID 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.byteid.ng/v1/kyc/requests \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/kyc/requests/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/business/cac \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/verifications \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/verifications/0b7c3f4e-8a61-4c1e-9f0a-6f3d2b1c9e77 \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/balance \
-H "Authorization: Bearer $BYTEID_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.byteid.ng/v1/pricing \
-H "Authorization: Bearer $BYTEID_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