> For the complete documentation index, see [llms.txt](https://docs.tendar.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tendar.co/documentation/onboarding/know-your-customer.md).

# Know Your Customer

Every lending, disbursement, or credit decision starts with one question: *is this person who they say they are?* The KYC (Know Your Customer) module in Tendar's Onboarding Service answers that question by letting you verify user identities against Nigerian government databases — BVN, NIN, virtual NIN, driver's license, international passport, and phone number — all through a single, unified API.

This guide walks you through everything you need to understand and implement KYC in your integration: the concepts behind it, the different identity types available, how verification works, and how to wire it all together in your product.

### How KYC works

***

At a high level, Tendar's KYC system follows a three-step pattern:

{% stepper %}
{% step %}

#### Look up an identity

You send a government-issued identifier (e.g. a BVN number) to the appropriate lookup endpoint. Tendar queries the national database and returns the identity details — name, date of birth, photo, address, and more.
{% endstep %}

{% step %}

#### A UID record is created

Every successful lookup produces a **UID** (Unique Identity Document) record. This is Tendar's internal representation of a verified identity. It stores the lookup results, tracks verification status, and links back to a user if you provided a `user_id`.
{% endstep %}

{% step %}

#### Verify the UID (optional)

A UID can be further verified via **OTP** (a code sent to the identity holder's email or phone) or **facial validation** (comparing a selfie against the photo on record). Once verified through one or both methods, the UID is marked as `verified: true`.

```
┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│  Identity Lookup  │────▶│   UID Created    │────▶│  Verify UID      │
│  (BVN, NIN, etc.) │     │  (details saved) │     │  (OTP / Face)    │
└──────────────────┘     └──────────────────┘     └──────────────────┘
```

This separation between *lookup* and *verification* is intentional. A lookup tells you what the government knows about a person. Verification confirms that the person standing in front of your app is actually that person. Depending on your compliance requirements, you might need just the lookup, just the OTP, or both — Tendar gives you the flexibility to decide.
{% endstep %}
{% endstepper %}

### Before you begin

***

Make sure you have:

* Your **secret key** from the [Tendar dashboard](https://app.tendar.co/services?service-level=api-keys-and-webhooks).
* A server-side environment to make API calls (never expose your secret key in client-side code).
* A **user already created** via the [User Management](/documentation/onboarding/user-management.md) API (optional, but recommended — linking a lookup to a `user_id` lets you build a full identity profile for each customer).
* Sufficient **wallet balance** — KYC lookups are billable actions. Each identity type has its own pricing tier on your subscription plan. See [Billing](#billing) for exactly when a lookup is charged.

Every request must include the `Authorization` header:

```http
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
```

### Supported identity types

***

Tendar supports identity verification for **Nigeria**. All KYC lookup endpoints live under the `/kyc/nigeria/` namespace.

| Identity Type          | Identifier Required | Facial Validation |             OTP Verification            |
| ---------------------- | ------------------- | :---------------: | :-------------------------------------: |
| BVN                    | 11-digit BVN number |         ✅         | ✅ when the record has an email or phone |
| NIN                    | 11-digit NIN number |         ✅         | ✅ when the record has an email or phone |
| Virtual NIN (vNIN)     | Virtual NIN string  |         —         | ✅ when the record has an email or phone |
| International Passport | Passport number     |         ✅         |      ✅ when the record has a phone      |
| Driver's License       | License number      |         —         |                    —                    |
| Phone Number           | Phone number        |         —         |                    ✅                    |

**What do these columns mean?**

* **Facial Validation** — The lookup can be paired with a selfie image. Tendar compares the selfie against the photo on the government record and returns a confidence score. If the match succeeds, the UID is automatically marked as verified via `face_validation`.
* **OTP Verification** — After the lookup, you can send a verification code to the email and/or phone number found on the identity record. The user submits the code back, and the UID is verified via `otp`. The UID's `can_be_verified_by` array tells you whether this is available for a given record.

{% hint style="warning" %}
**Currently unavailable:** CAC, voter's card, driver's license by image, driver's license with facial validation, and passport by image. Their endpoints still exist but return `400` with `"this service is currently unavailable"`, and you are **not charged**.
{% endhint %}

### The UID object

***

Every KYC lookup produces a UID. Understanding this object is key to working with the API effectively.

| Field                | Type      | Description                                                                                                                                                              |
| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                 | string    | Unique identifier for this UID record.                                                                                                                                   |
| `user`               | string    | Internal ID of the linked Tendar user. `null` when no `user_id` was provided in the lookup request.                                                                      |
| `user_id`            | string    | The `user_id` you passed in the request (empty when none was passed).                                                                                                    |
| `identity`           | string    | The identity type. Lookup responses return the identity type's internal ID; the [UID list](#listing-a-users-uids) returns its name (e.g. `"bvn"`, `"nin"`).              |
| `uid`                | string    | The identity number itself. Returned only for driver's license and phone number records; omitted otherwise.                                                              |
| `verified`           | boolean   | Whether this UID has been verified (via OTP, face validation, or both).                                                                                                  |
| `verified_at`        | timestamp | When the UID was verified. Omitted if not yet verified.                                                                                                                  |
| `verified_by`        | string\[] | Methods that have been used to verify this UID. Possible values: `"otp"`, `"face_validation"`.                                                                           |
| `can_be_verified_by` | string\[] | Methods available for verification. For example, `["face_validation", "otp"]`. `"otp"` is present only when the record has an email or phone number to send the code to. |
| `face_data`          | object    | Facial validation results. Contains `status` (bool), `confidence` (float), and `message`. `null` if not used.                                                            |
| `details`            | object    | The identity details returned from the government database. Shape varies by identity type (see sections below).                                                          |
| `email`              | string    | Email address found on the identity record (used for OTP delivery).                                                                                                      |
| `phone_number`       | string    | Phone number found on the identity record, in E.164 format (used for OTP delivery).                                                                                      |
| `expires_at`         | timestamp | When this lookup should be considered stale (30 days after the lookup).                                                                                                  |
| `created_at`         | timestamp | When this UID was created.                                                                                                                                               |
| `updated_at`         | timestamp | When this UID was last updated.                                                                                                                                          |

> **UID freshness:** `expires_at` is set 30 days after each lookup. It is a guide, not a hard limit — the record is not deleted and is still returned by the [UID list](#listing-a-users-uids) afterwards. If your compliance rules require current data, check `expires_at` and run a fresh lookup when it has passed; a repeat lookup for the same user refreshes the existing UID and moves `expires_at` forward.

### Looking up a BVN

***

The BVN (Bank Verification Number) is the most common KYC check in Nigerian fintech. Every bank customer has one, making it a near-universal identifier. Tendar offers two flavors: a standard lookup and a lookup with facial validation.

#### Standard BVN lookup

Send the user's 11-digit BVN to retrieve their identity details from the national BVN database.

**Endpoint**

```http
POST /api/v1/kyc/nigeria/bvn/lookup
```

**Request body**

```json
{
  "user_id": "usr-lyfriztafj",
  "bvn": "12345678901",
  "send_otp": false
}
```

**A few things to know:**

* **`user_id`** — Optional. If provided, Tendar links the resulting UID to this user. This is highly recommended — it lets you later list all identity verifications for a user and builds a complete KYC profile. The user must exist under your company; otherwise the API returns `404 "user not found"` before any lookup is made, and nothing is charged.
* **`send_otp`** — When `true`, Tendar automatically sends a 6-digit verification code to the email and phone number found on the BVN record (both, when both exist) immediately after the lookup. Each channel is billed as an OTP. If the code cannot be sent, the lookup still succeeds — send it later with the [send verification](#method-1-otp-verification) endpoint. When `false`, the UID is created but left unverified — you can trigger verification manually later.
* **Billing** — This lookup is charged against your wallet when the provider completes it — including when the BVN has no record (`404 "bvn data not found"`). You are not charged when the provider is unavailable. See [Billing](#billing).
* **Repeat lookups** — If you look up the same BVN for the same user again, Tendar refreshes the existing UID record rather than creating a duplicate, and keeps any verification it already had. **Every repeat lookup is charged again**, so don't retry a successful lookup — read the user's UIDs with the [UID list](#listing-a-users-uids) instead. A lookup without a `user_id` always creates a new UID.

**Response** `200 OK`

```json
{
  "data": {
    "created_at": "2024-06-26T00:54:21.686Z",
    "updated_at": "2024-06-26T00:54:21.686Z",
    "id": "667b66bdcd3ffc6b4119a623",
    "user": "667b655d5bf9a162a55e87c9",
    "user_id": "usr-lyfriztafj",
    "identity": "63da77393aeb994abb46cfe3",
    "verified": false,
    "verified_by": [],
    "can_be_verified_by": [
      "face_validation",
      "otp"
    ],
    "face_data": null,
    "details": {
      "first_name": "JOHN",
      "last_name": "DOE",
      "middle_name": "SMITH",
      "date_of_birth": "01/01/1990",
      "registration_date": "10/01/2020",
      "enrollment_bank": "058",
      "enrollment_branch": "",
      "email": "JOHNDOE@EXAMPLE.COM",
      "gender": "male",
      "level_of_account": "Level 1 - Low Level Accounts",
      "lga_of_origin": "Ikeja",
      "lga_of_residence": "Ikeja",
      "marital_status": "Single",
      "name_on_card": "DOE JOHN SMITH",
      "nationality": "Nigeria",
      "phone_number": "08012345678",
      "phone_number2": "",
      "residential_address": "No 1 John Doe Street, Ikeja, Lagos",
      "state_of_origin": "Lagos State",
      "state_of_residence": "Lagos State",
      "title": "Mr",
      "watch_listed": "NO",
      "image": "/9j/4AAQSkZJRg..."
    },
    "email": "johndoe@example.com",
    "phone_number": "+2348012345678",
    "expires_at": "2024-07-26T00:54:21.193Z"
  },
  "error": false,
  "message": "BVN lookup successful"
}
```

Notice that `verified` is `false` and `verified_by` is empty. The government confirmed the identity exists, but nobody has yet proved the person requesting this lookup *is* that person. That's where verification comes in.

Also note the `can_be_verified_by` array — it tells you which verification methods are available for this UID. In this case, both `face_validation` and `otp` are available.

#### BVN lookup with facial validation

This variant adds a layer of biometric confirmation. You provide a selfie (or any face image) of the user alongside their BVN. Tendar looks up the BVN, retrieves the photo on file, and performs a face comparison.

**Endpoint**

```http
POST /api/v1/kyc/nigeria/bvn/lookup/with-face
```

**Request body**

```json
{
  "user_id": "usr-lyfriztafj",
  "bvn": "12345678901",
  "image": "https://example.com/selfie.jpg",
  "send_otp": true
}
```

* **`image`** — The user's selfie, either as a publicly accessible URL or as a base64-encoded image (a `data:image/jpeg;base64,...` prefix is accepted). Tendar compares it against the BVN photo on record. The same applies to every `/with-face` lookup.

If the face matches, the response will include `face_data` with a confidence score, and the UID will be automatically verified via `face_validation`:

```json
{
  "data": {
    "...": "...",
    "verified": true,
    "verified_at": "2024-06-26T00:55:42.326Z",
    "verified_by": [
      "face_validation"
    ],
    "face_data": {
      "status": true,
      "confidence": 99.98,
      "message": "Face Match"
    },
    "...": "..."
  },
  "error": false,
  "message": "BVN lookup successful"
}
```

The `confidence` field indicates how closely the selfie matches the photo on file. A `status` of `true` means Tendar considers the match positive — base your decision on `status`, not on the wording of `message`, which varies with the verification provider (e.g. `"Face Match"` or `"face matched"`). If the face does not match, the lookup still succeeds and returns the identity details, with `face_data.status` set to `false` and the UID left unverified.

> **When to use facial validation:** Use the `/with-face` variant when you need a higher level of assurance — for example, before disbursing a loan, raising a transaction limit, or during high-risk onboarding flows. For lighter verification needs (e.g. pre-screening), the standard lookup combined with OTP is usually sufficient.

### Looking up a NIN

***

The NIN (National Identification Number) is Nigeria's primary civil identity number. It's richer in data than a BVN — it includes next-of-kin details, education level, employment status, and more. Tendar supports three NIN lookup methods.

#### Standard NIN lookup

**Endpoint**

```http
POST /api/v1/kyc/nigeria/nin/lookup
```

**Request body**

```json
{
  "user_id": "usr-lyfriztafj",
  "nin": "12345678901",
  "send_otp": true
}
```

#### NIN lookup with facial validation

**Endpoint**

```http
POST /api/v1/kyc/nigeria/nin/lookup/with-face
```

**Request body**

```json
{
  "user_id": "usr-lyfriztafj",
  "nin": "12345678901",
  "image": "https://example.com/selfie.jpg",
  "send_otp": false
}
```

#### Virtual NIN (vNIN) lookup

Nigeria's NIMC introduced Virtual NINs to let citizens share a tokenized version of their NIN without revealing the actual number. If your user provides a vNIN instead of a raw NIN, use this endpoint.

**Endpoint**

```http
POST /api/v1/kyc/nigeria/nin/virtual/lookup
```

**Request body**

```json
{
  "user_id": "usr-lyfriztafj",
  "virtual_nin": "AB12345678901234CD",
  "send_otp": true
}
```

All three NIN lookups return the same UID structure. The `details` object for a NIN lookup is significantly richer, including fields like `level_of_education`, `employment_status`, `profession`, `religion`, `nok_firstname`, `nok_surname`, and many more.

### Looking up other identity types

***

The remaining identity types follow the same pattern: send the identifier, get a UID back. Every lookup accepts the same optional `user_id` and `send_otp` fields as the BVN lookup, and the same rules on billing and repeat lookups apply.

#### International Passport

| Endpoint                                             | Required Fields   |
| ---------------------------------------------------- | ----------------- |
| `POST /api/v1/kyc/nigeria/passport/lookup`           | `number`          |
| `POST /api/v1/kyc/nigeria/passport/lookup/with-face` | `number`, `image` |

Include the passport holder's last name in `verification_data` — the passport registry matches on it:

```json
{
  "user_id": "usr-lyfriztafj",
  "number": "A12345678",
  "verification_data": {
    "last_name": "Doe"
  },
  "send_otp": false
}
```

The `details` object includes `first_name`, `last_name`, `middle_name`, `date_of_birth`, `gender`, `issued_date`, `expiry_date`, `issued_at`, `image`, `signature`, and `phone_number`.

#### Driver's License

```http
POST /api/v1/kyc/nigeria/drivers-license/lookup
```

Include the license holder's date of birth in `verification_data`, in `DD/MM/YYYY` format:

```json
{
  "user_id": "usr-lyfriztafj",
  "number": "ABC12345678",
  "verification_data": {
    "dob": "01/01/1990"
  },
  "send_otp": false
}
```

A `dob` in any other format returns `400` with `"verification_data.dob must be dd/mm/yyyy"`.

The `details` object includes `first_name`, `last_name`, `middle_name`, `gender`, `issued_date`, `expiry_date`, `issued_at`, and `image`. The license number is returned in the UID's `uid` field.

> **Note:** Driver's license records carry no email or phone number, so a driver's license UID cannot be verified by OTP.

#### Phone Number

Look up identity information using just a phone number.

```http
POST /api/v1/kyc/nigeria/phone-number/lookup
```

```json
{
  "user_id": "usr-lyfriztafj",
  "number": "+2348012345678",
  "send_otp": true
}
```

This returns a rich `details` object similar to a NIN lookup, since phone number records in Nigeria are linked to NIN data. The phone number is returned in the UID's `uid` field, and the UID can be verified by OTP only.

#### The `verification_data` object

Some lookups send extra holder information to the identity registry. It is optional on every lookup and ignored where it isn't used:

| Field       | Used by          | Description                               |
| ----------- | ---------------- | ----------------------------------------- |
| `last_name` | Passport         | The holder's last name.                   |
| `dob`       | Driver's license | The holder's date of birth, `DD/MM/YYYY`. |

#### Currently unavailable lookups

The following endpoints exist but are not currently served. They return `400` with `"this service is currently unavailable"` and you are **not charged**:

| Endpoint                                                    | Lookup                                   |
| ----------------------------------------------------------- | ---------------------------------------- |
| `POST /api/v1/kyc/nigeria/cac/lookup`                       | CAC (business registration) by RC number |
| `POST /api/v1/kyc/nigeria/voters-card/lookup`               | Voter's card                             |
| `POST /api/v1/kyc/nigeria/drivers-license/lookup/image`     | Driver's license from a document image   |
| `POST /api/v1/kyc/nigeria/drivers-license/lookup/with-face` | Driver's license with facial validation  |
| `POST /api/v1/kyc/nigeria/passport/lookup/image`            | Passport from a document image           |

### Verifying a UID

***

After a lookup, the UID record exists but may not be verified yet. Verification proves that the person performing the lookup is the actual owner of the identity. There are two verification methods:

#### Method 1 — OTP Verification

This is a two-step process, identical in concept to email/phone verification on users, but scoped to a specific UID record.

{% stepper %}
{% step %}

#### Step 1 — Send the verification code

```http
GET /api/v1/kyc/uid/send-verification/:uid_id
```

Replace `:uid_id` with the `id` of the UID returned from the lookup (e.g. `667b66bdcd3ffc6b4119a623`). Tendar sends a 6-digit code to the email and/or phone number found on the identity record. The code expires after **10 minutes**.

**Response**

```json
{
  "error": false,
  "message": "Verification token sent successfully"
}
```

When the record has both an email and a phone number, the same code is sent to both, and each channel is billed as an OTP.

> If the UID's `can_be_verified_by` array doesn't include `"otp"` (or the record has no email or phone number), this endpoint will return a `400` error with the message `"uid cannot be verified by otp"`.

**Optional query parameter:** add `?user_id=usr-lyfriztafj` to make sure the UID belongs to that user — if it doesn't, the API returns `404 "uid not found"`. The same parameter works on the verify endpoint below.
{% endstep %}

{% step %}

#### Step 2 — Submit the code

```http
POST /api/v1/kyc/uid/verify/:uid_id
```

**Request body**

```json
{
  "token": "391867"
}
```

**Response**

```json
{
  "error": false,
  "message": "Uid verified successfully"
}
```

Once verified, the UID's `verified` field flips to `true`, `verified_at` is populated, and `"otp"` is appended to the `verified_by` array.

A wrong, expired or already-used code returns `400` with `"invalid or expired otp provided"`. After **5 wrong codes** the code is cancelled (`"too many invalid attempts, please request a new otp"`) — send a new one with Step 1.
{% endstep %}
{% endstepper %}

#### Method 2 — Facial Validation

Unlike OTP, facial validation happens *during* the lookup itself. When you call any `/with-face` endpoint, Tendar performs the face comparison inline and, if the match succeeds, marks the UID as verified automatically. There is no separate "verify by face" endpoint — it's baked into the lookup.

This means a UID can end up verified by:

* OTP only
* Face validation only
* Both OTP and face validation

The `verified_by` array always reflects exactly which methods have been applied.

### Listing a user's UIDs

***

Once you've performed several KYC lookups for a user, you may want to see all their identity records in one place — for example, to build a compliance dashboard or to check what's already been verified before running another lookup.

**Endpoint**

```http
GET /api/v1/kyc/uid/list/:user_id
```

Replace `:user_id` with the user's ID (e.g. `usr-lyfriztafj`).

**Response** `200 OK`

```json
{
  "data": [
    {
      "created_at": "2024-06-26T00:54:21.686Z",
      "updated_at": "2024-06-26T00:55:43.307Z",
      "id": "667b66bdcd3ffc6b4119a623",
      "user": "667b655d5bf9a162a55e87c9",
      "user_id": "usr-lyfriztafj",
      "identity": "bvn",
      "verified": true,
      "verified_at": "2024-06-26T00:55:42.326Z",
      "verified_by": ["face_validation"],
      "can_be_verified_by": ["face_validation", "otp"],
      "face_data": {
        "status": true,
        "confidence": 99.98,
        "message": "Face Match"
      },
      "details": { "...": "..." },
      "email": "johndoe@example.com",
      "phone_number": "+2348012345678",
      "expires_at": "2024-07-26T00:55:42.574Z"
    },
    {
      "...": "another UID record"
    }
  ],
  "error": false,
  "message": "Uids fetched successfully"
}
```

This endpoint returns all UIDs across all identity types — BVN, NIN, passport, etc. — in a single, unpaginated list, each with its `details` decoded. In this list, `identity` is the identity type's name: `bvn`, `nin`, `international_passport`, `drivers_license` or `phone_number`. A user with no lookups (or a `user_id` that doesn't exist) returns an empty array.

### Listing supported identity types

***

If you want to dynamically render a list of available identity checks in your UI (e.g. a dropdown of verification methods), you can fetch the supported identity types from the API.

**Endpoint**

```http
GET /api/v1/kyc/identity/list
```

**Response** `200 OK`

```json
{
  "data": [
    {
      "id": "63da77393aeb994abb46cfe3",
      "name": "bvn",
      "description": "Bank Verification Number",
      "image": "https://...",
      "active": true,
      "countries": ["nigeria"],
      "continents": ["africa"],
      "created_at": "2023-02-01T14:30:17.000Z",
      "updated_at": "2023-02-01T14:30:17.000Z"
    }
  ],
  "error": false,
  "message": "Identities fetched successfully"
}
```

Each identity type includes a `name`, `description`, `image`, `active` status, and the `countries`/`continents` it's available in. When BVN or NIN is `active: false`, their lookups (including with-face and virtual NIN) are temporarily disabled and return `400` with `"<name> identity has been disabled, please try again later"` — you are not charged.

The list also contains `voters_card` and `cac_number`; those lookups are [currently unavailable](#currently-unavailable-lookups) regardless of their `active` flag.

### Putting it all together

***

Here's a realistic onboarding flow that incorporates KYC:

{% stepper %}
{% step %}

#### Create the user

```bash
curl -X POST {{base_url}}/api/v1/user/create \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Doe",
    "email": "johndoe@example.com",
    "phone": "+2348012345678",
    "date_of_birth": "01/01/1990",
    "gender": "male",
    "country": "nigeria"
  }'
```

{% endstep %}

{% step %}

#### Run a BVN lookup with facial validation

```bash
curl -X POST {{base_url}}/api/v1/kyc/nigeria/bvn/lookup/with-face \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "usr-lyfriztafj",
    "bvn": "12345678901",
    "image": "https://example.com/selfie.jpg",
    "send_otp": true
  }'
```

At this point, if the face matches, the UID is already verified via `face_validation`. Setting `send_otp: true` means an OTP is also sent — giving you a second verification layer.
{% endstep %}

{% step %}

#### Complete OTP verification

Your user receives the code via SMS/email and submits it through your UI.

```bash
curl -X POST {{base_url}}/api/v1/kyc/uid/verify/667b66bdcd3ffc6b4119a623 \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "token": "391867" }'
```

Now the UID is verified by both `face_validation` and `otp` — the highest level of assurance.
{% endstep %}

{% step %}

#### Check the user's full KYC profile

```bash
curl -X GET {{base_url}}/api/v1/kyc/uid/list/usr-lyfriztafj \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"
```

You can use this to power compliance dashboards, risk decisions, or simply to determine whether a user has completed KYC before granting them access to other Tendar services like Credit Score, Disbursement, or BNPL.
{% endstep %}
{% endstepper %}

### Billing

***

KYC lookups are billable actions. Each identity type and verification method has its own price point, which is determined by your company's subscription plan. Here's what to keep in mind:

* **You pay when the identity registry was searched.** A lookup that returns identity details is charged, and so is one where the registry has no record of the identifier (`404 "<identity> data not found"`) — the search was still performed. An identifier the provider rejects as invalid (`400`) is charged only when the provider charged for it.
* **You don't pay when no search happens**, including when:
  * a required field is missing or invalid;
  * the `user_id` doesn't exist;
  * the identity type is disabled;
  * the lookup is [currently unavailable](#currently-unavailable-lookups);
  * the verification provider is unavailable (`503`) — it is safe to retry later.
* **Repeat lookups are charged each time**, even for the same identifier and user. Use the [UID list](#listing-a-users-uids) to read results you already have.
* Facial validation variants may have a different price than standard lookups.
* OTP delivery (when triggered via `send_otp: true` or via the `/send-verification` endpoint) is billed as an OTP, per channel — email and SMS are charged separately when the record has both.
* Make sure your wallet has sufficient balance before making KYC calls. If your balance is insufficient, the API returns `400` with `"insufficient balance, please top up your wallet."` and no lookup is made.

### Error handling

***

KYC endpoints can return several types of errors. Here are the most common:

| Scenario                                                  | HTTP Status | Message                                                                                                                                                                |
| --------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Missing required field                                    | 400         | Names the field, e.g. `"the bvn field is required"`, `"the image field is required"`                                                                                   |
| Driver's license `verification_data.dob` not `DD/MM/YYYY` | 400         | `"verification_data.dob must be dd/mm/yyyy"`                                                                                                                           |
| Lookup is currently unavailable                           | 400         | `"this service is currently unavailable"`                                                                                                                              |
| Identity type has been temporarily disabled               | 400         | `"bvn identity has been disabled, please try again later"` (or `nin`)                                                                                                  |
| The registry rejected the identifier (e.g. malformed)     | 400         | The registry's own message                                                                                                                                             |
| User not found (invalid `user_id`)                        | 404         | `"user not found"`                                                                                                                                                     |
| Identity data not found                                   | 404         | `"bvn data not found"`, `"nin data not found"` (NIN and virtual NIN), `"passport data not found"`, `"drivers license data not found"`, `"phone number data not found"` |
| UID not found (invalid UID ID, or not the given user's)   | 404         | `"uid not found"`                                                                                                                                                      |
| UID cannot be verified by OTP                             | 400         | `"uid cannot be verified by otp"`                                                                                                                                      |
| Wrong, expired or already-used verification code          | 400         | `"invalid or expired otp provided"`                                                                                                                                    |
| Too many wrong verification codes (5)                     | 400         | `"too many invalid attempts, please request a new otp"`                                                                                                                |
| Insufficient wallet balance                               | 400         | `"insufficient balance, please top up your wallet."`                                                                                                                   |
| Verification provider unavailable                         | 503         | `"this request could not be processed at the moment. Please try again later"`                                                                                          |

A `503` means the lookup could not be completed on the identity provider's side. You are not charged, and you can retry after a short wait.

All errors follow the standard Tendar error format:

```json
{
  "error": true,
  "message": "A description of what went wrong"
}
```

### API Reference

***

#### Nigeria — Identity Lookups

| Endpoint                                        | Method | Description                                          |
| ----------------------------------------------- | ------ | ---------------------------------------------------- |
| `/kyc/nigeria/bvn/lookup`                       | POST   | Look up a Bank Verification Number (BVN)             |
| `/kyc/nigeria/bvn/lookup/with-face`             | POST   | BVN lookup with facial validation                    |
| `/kyc/nigeria/nin/lookup`                       | POST   | Look up a National Identification Number (NIN)       |
| `/kyc/nigeria/nin/lookup/with-face`             | POST   | NIN lookup with facial validation                    |
| `/kyc/nigeria/nin/virtual/lookup`               | POST   | Look up a Virtual NIN (vNIN)                         |
| `/kyc/nigeria/drivers-license/lookup`           | POST   | Look up a driver's license by number                 |
| `/kyc/nigeria/passport/lookup`                  | POST   | Look up an international passport by number          |
| `/kyc/nigeria/passport/lookup/with-face`        | POST   | Passport lookup with facial validation               |
| `/kyc/nigeria/phone-number/lookup`              | POST   | Look up identity by phone number                     |
| `/kyc/nigeria/cac/lookup`                       | POST   | CAC registration — *currently unavailable*           |
| `/kyc/nigeria/voters-card/lookup`               | POST   | Voter's card — *currently unavailable*               |
| `/kyc/nigeria/drivers-license/lookup/image`     | POST   | Driver's license by image — *currently unavailable*  |
| `/kyc/nigeria/drivers-license/lookup/with-face` | POST   | Driver's license with face — *currently unavailable* |
| `/kyc/nigeria/passport/lookup/image`            | POST   | Passport by image — *currently unavailable*          |

#### UID Management

| Endpoint                         | Method | Description                                                     |
| -------------------------------- | ------ | --------------------------------------------------------------- |
| `/kyc/uid/send-verification/:id` | GET    | Send a verification OTP for a UID record (optional `?user_id=`) |
| `/kyc/uid/verify/:id`            | POST   | Verify a UID using the OTP token (optional `?user_id=`)         |
| `/kyc/uid/list/:user_id`         | GET    | List all UID records for a user                                 |
| `/kyc/identity/list`             | GET    | List all identity types                                         |

### What's next

***

* [User Management](/documentation/onboarding/user-management.md) — Learn how to create users before running KYC.
* [OTP](/documentation/onboarding/one-time-password.md) — Understand the OTP system that powers UID verification.
* [Overview](/documentation/onboarding/overview.md) — Go back to the full Onboarding Service overview.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.tendar.co/documentation/onboarding/know-your-customer.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
