# 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, driver's license, international passport, voter's card, CAC registration, 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](https://docs.tendar.co/documentation/onboarding/user-management) 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.

Every request must include the `Authorization` header:

```http
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
```

### Supported identity types

***

Tendar currently supports identity verification for **Nigeria**. The system is designed to be country-extensible, but today all KYC endpoints live under the `/kyc/nigeria/` namespace.

| Identity Type          | Identifier Required | Facial Validation | Image Lookup | OTP Capable |
| ---------------------- | ------------------- | :---------------: | :----------: | :---------: |
| BVN                    | 11-digit BVN number |         ✅         |       —      |      ✅      |
| NIN                    | 11-digit NIN number |         ✅         |       —      |      ✅      |
| Virtual NIN (vNIN)     | Virtual NIN string  |         —         |       —      |      ✅      |
| Driver's License       | License number      |         ✅         |       ✅      |      ✅      |
| International Passport | Passport number     |         ✅         |       ✅      |      ✅      |
| Voter's Card           | Voter's card number |         —         |       —      |      ✅      |
| CAC                    | RC 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`.
* **Image Lookup** — Instead of (or alongside) a text identifier, you can submit an image of the physical document. Tendar extracts the identity details from the image.
* **OTP Capable** — After the lookup, you can send a verification code to the email or phone number found on the identity record. The user submits the code back, and the UID is verified via `otp`.

### 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 (if a `user_id` was provided in the lookup request).                      |
| `user_id`            | string    | The `user_id` you passed in the request.                                                                        |
| `identity`           | string    | Internal ID of the identity type (e.g. BVN, NIN).                                                               |
| `verified`           | boolean   | Whether this UID has been verified (via OTP, face validation, or both).                                         |
| `verified_at`        | timestamp | When the UID was verified. `null` 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, `["otp", "face_validation"]`.                                  |
| `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 UID record expires (30 days from creation).                                                           |
| `created_at`         | timestamp | When this UID was created.                                                                                      |
| `updated_at`         | timestamp | When this UID was last updated.                                                                                 |

> **UID Expiry:** UID records expire 30 days after creation. After expiry, the data is no longer served — you'll need to perform a fresh lookup. This ensures the identity data your platform relies on stays reasonably current.

### 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.
* **`send_otp`** — When `true`, Tendar automatically sends a 6-digit verification code to the email and/or phone number found on the BVN record immediately after the lookup. When `false`, the UID is created but left unverified — you can trigger verification manually later.
* **Billing** — This lookup is charged against your wallet. If the lookup fails (e.g. invalid BVN), the charge is not rolled back.
* **Idempotency** — If you look up the same BVN for the same user again, Tendar updates the existing UID record rather than creating a duplicate. This means retries are safe.

**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`** — A publicly accessible URL to the user's selfie. Tendar downloads this image and compares it against the BVN photo on record.

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 is a percentage (0–100) indicating how closely the selfie matches the photo on file. A `status` of `true` means Tendar considers the match positive.

> **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. Here's a quick reference for each.

#### Driver's License

Available in three flavors: number lookup, image lookup, and lookup with facial validation.

| Endpoint                                                    | Required Fields                                     |
| ----------------------------------------------------------- | --------------------------------------------------- |
| `POST /api/v1/kyc/nigeria/drivers-license/lookup`           | `number`, `user_id` (optional), `send_otp`          |
| `POST /api/v1/kyc/nigeria/drivers-license/lookup/image`     | `image`, `user_id` (optional), `send_otp`           |
| `POST /api/v1/kyc/nigeria/drivers-license/lookup/with-face` | `number`, `image`, `user_id` (optional), `send_otp` |

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

#### International Passport

Also available in three flavors.

| Endpoint                                             | Required Fields                                     |
| ---------------------------------------------------- | --------------------------------------------------- |
| `POST /api/v1/kyc/nigeria/passport/lookup`           | `number`, `user_id` (optional), `send_otp`          |
| `POST /api/v1/kyc/nigeria/passport/lookup/image`     | `image`, `user_id` (optional), `send_otp`           |
| `POST /api/v1/kyc/nigeria/passport/lookup/with-face` | `number`, `image`, `user_id` (optional), `send_otp` |

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`.

#### Voter's Card

```http
POST /api/v1/kyc/nigeria/voters-card/lookup
```

```json
{
  "user_id": "usr-lyfriztafj",
  "number": "90F5B1C48D234567890",
  "send_otp": false
}
```

#### CAC (Corporate Affairs Commission)

Used for business identity verification. Returns company registration details rather than personal identity data.

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

```json
{
  "user_id": "",
  "number": "1234567",
  "send_otp": false
}
```

The `details` object includes `rc_number`, `company_name`, `state`, `company_status`, `city`, `branch_address`, `lga`, and `email`.

> **Note:** CAC lookups don't return a personal photo, so facial validation and OTP-based verification are not applicable.

#### 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.

### 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"
}
```

> If the UID's `can_be_verified_by` array doesn't include `"otp"`, this endpoint will return a `400` error with the message `"uid cannot be verified by otp"`.
> {% 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.
{% 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": "63da77393aeb994abb46cfe3",
      "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 list.

### 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
```

Each identity type includes a `name`, `description`, `image`, `active` status, and the `countries`/`continents` it's available in. If `active` is `false`, lookups for that identity type are temporarily disabled.

### 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:

* Charges are **initiated before the lookup** and **finalized after**. If the lookup fails (e.g. invalid identifier, disabled identity type), the charge still goes through — you're billed for failed lookups.
* 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) may carry its own charge.
* Make sure your wallet has sufficient balance before making KYC calls. If your balance is insufficient, the API will return an error.

### Error handling

***

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

| Scenario                                       | HTTP Status | Message                                                    |
| ---------------------------------------------- | ----------- | ---------------------------------------------------------- |
| Identity type has been temporarily disabled    | 400         | `"bvn identity has been disabled, please try again later"` |
| User not found (invalid `user_id`)             | 404         | `"user not found"`                                         |
| Identity data not found (invalid identifier)   | 404         | `"bvn data not found"`                                     |
| UID not found (invalid UID ID)                 | 404         | `"uid not found"`                                          |
| UID cannot be verified by the requested method | 400         | `"uid cannot be verified by otp"`                          |
| Invalid or expired OTP token                   | 400         | `"invalid token"`                                          |
| Insufficient wallet balance                    | 400         | Charge-related error                                       |

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/cac/lookup`                       | POST   | Look up a CAC registration number              |
| `/kyc/nigeria/drivers-license/lookup`           | POST   | Look up a driver's license by number           |
| `/kyc/nigeria/drivers-license/lookup/image`     | POST   | Driver's license lookup via document image     |
| `/kyc/nigeria/drivers-license/lookup/with-face` | POST   | Driver's license lookup with facial validation |
| `/kyc/nigeria/passport/lookup`                  | POST   | Look up an international passport by number    |
| `/kyc/nigeria/passport/lookup/image`            | POST   | Passport lookup via document image             |
| `/kyc/nigeria/passport/lookup/with-face`        | POST   | Passport lookup with facial validation         |
| `/kyc/nigeria/voters-card/lookup`               | POST   | Look up a voter's card                         |
| `/kyc/nigeria/phone-number/lookup`              | POST   | Look up identity by phone number               |

#### UID Management

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

### What's next

***

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