Appearance
Endpoints
Every endpoint of the Open API, generated from the FOID backend. The examples are real responses from a test-mode run (sandbox data). Base URL: https://api.foid.ng/api/open/v1 (locally http://localhost:8000/api/open/v1). Authenticate with Authorization: Bearer FOID_TEST_… (or FOID_LIVE_…) — except the token endpoint, which uses the sign-in app's client credentials. See Overview for auth, errors and limits.
Connection
GET /ping
Response 200
json
{
"ok": true,
"organisation": "Acme Wallet",
"env": "test"
}Verifications
Hosted verification: create a link, send the person to it, read the result.
POST /verifications
Optional header: Idempotency-Key — repeat the same request safely (same key + same body = same result).
JSON body
| Field | Type | Required | Notes |
|---|---|---|---|
customer_ref | string | Your own id for this person; max length 128 | |
subject_id | string | ||
first_name | string | max length 80 | |
middle_name | string | max length 80 | |
last_name | string | max length 80 | |
date_of_birth | string | pattern ^\d{4}-\d{2}-\d{2}$ | |
email | string (email) | ||
phone | string | max length 20 | |
checks | array of nin | bvn | liveness | reverify | yes | min items 1 |
scopes | array of contact | financial | ||
purpose | string | yes | Shown to the person on the consent screen; min length 3; max length 200 |
return_url | string | max length 500 | |
metadata | object |
Example request
json
{
"checks": [
"nin",
"liveness"
],
"purpose": "Open a wallet",
"customer_ref": "cust_1001",
"first_name": "Ada",
"last_name": "Obi",
"return_url": "https://acme.example/onboarding/done"
}Response 201
json
{
"id": "d9588b86-660a-4737-a7a4-ff70ef99ed35",
"object": "verification",
"env": "test",
"status": "pending",
"outcome": null,
"checks": [
"nin",
"liveness"
],
"scopes": [
"identity",
"liveness"
],
"purpose": "Open a wallet",
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"customer_ref": "cust_1001",
"consent": {
"status": "none",
"scopes": []
},
"reused_foid_credential": false,
"results": {
"nin": {
"status": "pending"
},
"liveness": {
"status": "pending"
}
},
"metadata": {},
"expires_at": "2026-11-04T20:25:01.979339+00:00",
"completed_at": null,
"created_at": "2026-11-03T20:25:01.979556+00:00",
"hosted_url": "https://app.foid.ng/verify/vs_\u2026",
"client_token": "vs_\u2026"
}GET /verifications
| Parameter | In | Type | Required |
|---|---|---|---|
status | query | string | |
customer_ref | query | string | |
limit | query | integer | |
offset | query | integer |
Response 200
json
{
"object": "list",
"data": [
{
"id": "d9588b86-660a-4737-a7a4-ff70ef99ed35",
"object": "verification",
"env": "test",
"status": "pending",
"outcome": null,
"checks": [
"nin",
"liveness"
],
"scopes": [
"identity",
"liveness"
],
"purpose": "Open a wallet",
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"customer_ref": "cust_1001",
"consent": {
"status": "none",
"scopes": []
},
"reused_foid_credential": false,
"results": {
"nin": {
"status": "pending"
},
"liveness": {
"status": "pending"
}
},
"metadata": {},
"expires_at": "2026-11-04T20:25:01.979339+00:00",
"completed_at": null,
"created_at": "2026-11-03T20:25:01.979556+00:00",
"hosted_url": "https://app.foid.ng/verify/vs_\u2026",
"client_token": "vs_\u2026"
}
],
"has_more": false
}GET /verifications/{verification_id}
| Parameter | In | Type | Required |
|---|---|---|---|
verification_id | path | string | yes |
Response 200
json
{
"id": "d9588b86-660a-4737-a7a4-ff70ef99ed35",
"object": "verification",
"env": "test",
"status": "pending",
"outcome": null,
"checks": [
"nin",
"liveness"
],
"scopes": [
"identity",
"liveness"
],
"purpose": "Open a wallet",
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"customer_ref": "cust_1001",
"consent": {
"status": "none",
"scopes": []
},
"reused_foid_credential": false,
"results": {
"nin": {
"status": "pending"
},
"liveness": {
"status": "pending"
}
},
"metadata": {},
"expires_at": "2026-11-04T20:25:01.979339+00:00",
"completed_at": null,
"created_at": "2026-11-03T20:25:01.979556+00:00",
"hosted_url": "https://app.foid.ng/verify/vs_\u2026",
"client_token": "vs_\u2026"
}POST /verifications/{verification_id}/cancel
| Parameter | In | Type | Required |
|---|---|---|---|
verification_id | path | string | yes |
Response 200
json
{
"id": "d9588b86-660a-4737-a7a4-ff70ef99ed35",
"object": "verification",
"env": "test",
"status": "cancelled",
"outcome": null,
"checks": [
"nin",
"liveness"
],
"scopes": [
"identity",
"liveness"
],
"purpose": "Open a wallet",
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"customer_ref": "cust_1001",
"consent": {
"status": "none",
"scopes": []
},
"reused_foid_credential": false,
"results": {
"nin": {
"status": "pending"
},
"liveness": {
"status": "pending"
}
},
"metadata": {},
"expires_at": "2026-11-04T20:25:01.979339+00:00",
"completed_at": null,
"created_at": "2026-11-03T20:25:01.979556+00:00"
}Checks
Server-to-server checks. You attest you collected the person's consent.
POST /checks/nin
Look up an NIN and match it against the name / date of birth you send.
Optional header: Idempotency-Key — repeat the same request safely (same key + same body = same result).
JSON body
| Field | Type | Required | Notes |
|---|---|---|---|
customer_ref | string | Your own id for this person; max length 128 | |
subject_id | string | ||
first_name | string | max length 80 | |
middle_name | string | max length 80 | |
last_name | string | max length 80 | |
date_of_birth | string | pattern ^\d{4}-\d{2}-\d{2}$ | |
email | string (email) | ||
phone | string | max length 20 | |
number | string | yes | 11-digit NIN or BVN |
consent | object | yes | |
consent.obtained | true | yes | You confirm you collected the person's consent for this check |
consent.purpose | string | yes | min length 3; max length 200 |
Example request
json
{
"number": "12345678901",
"first_name": "Ada",
"last_name": "Obi",
"date_of_birth": "1994-03-12",
"customer_ref": "cust_1001",
"consent": {
"obtained": true,
"purpose": "Account opening"
}
}Response 200
json
{
"id": "880af60a-e95c-4679-b205-21e710d775e9",
"type": "nin",
"status": "passed",
"reasons": [],
"data": {
"method": "number",
"nin_last4": "8901",
"name_match": true,
"name_score": 1.0,
"dob_match": true,
"record": {
"first_name": "Ada",
"middle_name": null,
"last_name": "Obi",
"date_of_birth": "1994-03-12"
}
},
"provider": "mock",
"verification_id": null,
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"created_at": "2026-11-03T20:25:02.091651+00:00",
"customer_ref": "cust_1001"
}POST /checks/bvn
Look up a BVN and match it against the name / date of birth you send.
Optional header: Idempotency-Key — repeat the same request safely (same key + same body = same result).
JSON body
| Field | Type | Required | Notes |
|---|---|---|---|
customer_ref | string | Your own id for this person; max length 128 | |
subject_id | string | ||
first_name | string | max length 80 | |
middle_name | string | max length 80 | |
last_name | string | max length 80 | |
date_of_birth | string | pattern ^\d{4}-\d{2}-\d{2}$ | |
email | string (email) | ||
phone | string | max length 20 | |
number | string | yes | 11-digit NIN or BVN |
consent | object | yes | |
consent.obtained | true | yes | You confirm you collected the person's consent for this check |
consent.purpose | string | yes | min length 3; max length 200 |
Example request
json
{
"number": "12345678901",
"first_name": "Ada",
"last_name": "Obi",
"date_of_birth": "1994-03-12",
"customer_ref": "cust_1001",
"consent": {
"obtained": true,
"purpose": "Account opening"
}
}Response 200
json
{
"id": "c9e2b5a1-8322-4f2b-b6c6-5795a4b275f4",
"type": "bvn",
"status": "passed",
"reasons": [],
"data": {
"method": "number",
"bvn_last4": "8901",
"name_match": true,
"name_score": 1.0,
"dob_match": true,
"record": {
"first_name": "Ada",
"middle_name": null,
"last_name": "Obi",
"date_of_birth": "1994-03-12"
}
},
"provider": "mock",
"verification_id": null,
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"created_at": "2026-11-03T20:25:02.114253+00:00",
"customer_ref": "cust_1001"
}POST /checks/nin-document
Extract the NIN from a document image (OCR), then look it up. The document photo is not used.
multipart form
| Field | Type | Required | Notes |
|---|---|---|---|
file | string | yes | Photo/scan of an NIN slip or NIMC card |
consent_obtained | boolean | yes | |
purpose | string | yes | min length 3; max length 200 |
customer_ref | string | ||
subject_id | string | ||
first_name | string | ||
middle_name | string | ||
last_name | string | ||
date_of_birth | string |
Returns the same shape as POST /checks/nin, plus the NIN read from the document (last 4 only).
Subjects
The people you have verified. Every read goes through the person's consent.
GET /subjects
| Parameter | In | Type | Required |
|---|---|---|---|
customer_ref | query | string | |
limit | query | integer | |
offset | query | integer |
Response 200
json
{
"object": "list",
"data": [
{
"id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"customer_ref": "cust_1001",
"env": "test",
"consent": {
"status": "active",
"scopes": [
"identity"
]
},
"has_foid_account": false,
"created_at": "2026-11-03T20:25:01.976005+00:00",
"first_name": "Ada",
"middle_name": null,
"last_name": "Obi",
"date_of_birth": "1994-03-12",
"nin": {
"verified": true,
"last4": "8901"
},
"bvn": {
"verified": true,
"last4": "8901"
}
}
]
}GET /subjects/{subject_id}
| Parameter | In | Type | Required |
|---|---|---|---|
subject_id | path | string | yes |
Response 200
json
{
"id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"customer_ref": "cust_1001",
"env": "test",
"consent": {
"status": "active",
"scopes": [
"identity"
]
},
"has_foid_account": false,
"created_at": "2026-11-03T20:25:01.976005+00:00",
"first_name": "Ada",
"middle_name": null,
"last_name": "Obi",
"date_of_birth": "1994-03-12",
"nin": {
"verified": true,
"last4": "8901"
},
"bvn": {
"verified": true,
"last4": "8901"
}
}GET /subjects/{subject_id}/checks
| Parameter | In | Type | Required |
|---|---|---|---|
subject_id | path | string | yes |
Response 200
json
{
"object": "list",
"data": [
{
"id": "c9e2b5a1-8322-4f2b-b6c6-5795a4b275f4",
"type": "bvn",
"status": "passed",
"reasons": [],
"data": {
"method": "number",
"bvn_last4": "8901",
"name_match": true,
"name_score": 1.0,
"dob_match": true,
"record": {
"first_name": "Ada",
"middle_name": null,
"last_name": "Obi",
"date_of_birth": "1994-03-12"
}
},
"provider": "mock",
"verification_id": null,
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"created_at": "2026-11-03T20:25:02.114253+00:00"
},
{
"id": "880af60a-e95c-4679-b205-21e710d775e9",
"type": "nin",
"status": "passed",
"reasons": [],
"data": {
"method": "number",
"nin_last4": "8901",
"name_match": true,
"name_score": 1.0,
"dob_match": true,
"record": {
"first_name": "Ada",
"middle_name": null,
"last_name": "Obi",
"date_of_birth": "1994-03-12"
}
},
"provider": "mock",
"verification_id": null,
"subject_id": "c9981038-6db3-4236-8fca-d8bbb89dadfa",
"created_at": "2026-11-03T20:25:02.091651+00:00"
}
]
}GET /subjects/{subject_id}/trust-report
Financial trust signals + living credential state. FOID account holders only, scope financial.
| Parameter | In | Type | Required |
|---|---|---|---|
subject_id | path | string | yes |
Response 403
json
{
"error": {
"type": "permission",
"code": "scope_not_granted",
"message": "No active consent for scope 'financial'"
}
}DELETE /subjects/{subject_id}
Erase a subject and everything FOID holds about them for your business (checks, face, consent).
| Parameter | In | Type | Required |
|---|---|---|---|
subject_id | path | string | yes |
Response 204 — no body
Continue with FOID
Exchange the one-time code from the sign-in popup. Client credentials, not the API key.
POST /oauth/token
Form body (application/x-www-form-urlencoded or JSON)
| Field | Required | Notes |
|---|---|---|
grant_type | yes | authorization_code |
code | yes | The one-time code from the popup. Single use, expires after 60 seconds. |
code_verifier | yes | The PKCE verifier that matches the code_challenge used to start sign-in. |
redirect_uri | redirect mode | Exactly the one used to start sign-in (response_mode=query). |
client_id + client_secret | one auth method | Or HTTP Basic client_id:client_secret (recommended). |
client_assertion_type + client_assertion | one auth method | private_key_jwt: an ES256/RS256 JWT, iss=sub=client ID, aud=this URL, expires ≤ 5 min. |
Errors use OAuth's format: {"error": "invalid_grant", "error_description": "…"}.
Response 200
json
{
"object": "foid_sign_in",
"env": "test",
"sandbox": true,
"nonce": "n_51b7",
"subject_id": "ce669b19-ced4-4ff9-ac99-01d560450380",
"scopes": [
"contact",
"identity",
"profile"
],
"foid_account": true,
"face_rematched_at": null,
"subject": {
"id": "ce669b19-ced4-4ff9-ac99-01d560450380",
"customer_ref": null,
"env": "test",
"consent": {
"status": "active",
"scopes": [
"contact",
"identity",
"profile"
]
},
"has_foid_account": true,
"created_at": "2026-11-03T20:25:02.497513+00:00",
"first_name": "Chiamaka",
"middle_name": null,
"last_name": "Eze",
"date_of_birth": "1990-01-01",
"nin": {
"verified": true,
"last4": "0001"
},
"bvn": {
"verified": true,
"last4": "0002"
},
"email": "sandbox.person@example.com",
"phone": "**********0001"
},
"checks": []
}