> For the complete documentation index, see [llms.txt](https://docs.fonbnk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fonbnk.com/server-to-server/api-endpoints/submit-user-kyc.md).

# Submit user KYC

## <mark style="color:$warning;">\[POST]</mark> /api/v2/user/kyc

Submits one KYC document for a user. Pick the document from the `kycDocuments` array that [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) returned, collect its `requiredFields`, and post them here.

{% hint style="warning" %}
This endpoint does not create users. An email we have not seen before is a `404 User not found` — call [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) first, which registers the user. It also needs the same create-users permission; without it every call is a `403`.
{% endhint %}

### Request

```typescript
type RequestBody = {
  userEmail: string;           // required
  documentId: string;          // required, the _id of a document from kycDocuments
  userFields: Record<string, any>; // required, keyed by the document's requiredFields keys
  countryIsoCode?: string;     // country of the order this KYC is for
  kycCountryIsoCode?: string;  // country the user chose to verify in, if they chose one
}
```

`countryIsoCode` and `kycCountryIsoCode` mirror the read endpoint. Both are optional and fall back to the user's stored country. Pass the same pair you passed to [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) so the document you submit is one that endpoint offered.

The country rules only run when you send `kycCountryIsoCode`. When you do, a **basic** document is accepted only if `kycCountryIsoCode` equals `countryIsoCode` and we process payments in that country; otherwise it is a `400` and the user needs an advanced document. Omit `kycCountryIsoCode` and neither check applies. Separately, and always: a basic pass only satisfies orders in the country it was earned in.

### Basic KYC

Basic documents ask for a name, a date of birth and an ID number. Honour each field's `regexp` and `format` — they are the provider's own rules and a mismatch is rejected before we ever reach them.

Request body example. This is Nigeria's BVN, the only enabled Nigerian document:

```json
{
    "userEmail": "user@example.com",
    "countryIsoCode": "NG",
    "documentId": "67da90f0b6f2529a09645219",
    "userFields": {
        "first_name": "John",
        "last_name": "Doe",
        "dob": "1990-01-01",
        "id_number": "12345678901"
    }
}
```

### Advanced KYC

Advanced documents replace the ID number with an `images` array. Its field has `type: "smile-identity-images"` in `requiredFields`. Each entry is an object with an `image_type_id` and an `image`, forwarded to Smile Identity unchanged:

| `image_type_id` | What it is            |
| --------------- | --------------------- |
| `2`             | Selfie                |
| `3`             | Front of the document |
| `7`             | Back of the document  |

{% hint style="warning" %}
**Images must be base64 strings.** File uploads and image URLs are not accepted, and those three IDs are the base64 slots specifically. The whole request body, images included, must stay under **10 MB** — compress before encoding.
{% endhint %}

Send the back of the document only when it has one.

Request body example, using Kenya's national ID:

{% code overflow="wrap" expandable="true" %}

```json
{
  "userEmail": "user@example.com",
  "countryIsoCode": "KE",
  "documentId": "67da93dd487b0fc92fba04a4",
  "userFields": {
    "first_name": "John",
    "last_name": "Doe",
    "dob": "1990-01-01",
    "images": [
      { "image_type_id": 2, "image": "<base64-encoded-selfie>" },
      { "image_type_id": 3, "image": "<base64-encoded-document-front>" },
      { "image_type_id": 7, "image": "<base64-encoded-document-back>" }
    ]
  }
}
```

{% endcode %}

{% hint style="info" %}
Which documents exist is per country and per tier, and a document that exists can still be switched off. Nigeria today enables **only** the basic BVN, so there is no Nigerian advanced document to submit at all: an NG user who needs `advanced` has to go through support. Never hard-code a `documentId` — read `kycDocuments` and submit what it offers.
{% endhint %}

### Response

The response is the same shape as [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md), re-read after the submission. Verification is asynchronous, so it comes back with `currentKycStatus: "initiated"`.

One difference worth knowing: the re-read carries no amounts, so its `requiredKycType` is the history-only answer. It is not a verdict on the order you are about to create. Ask [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) again with the four amount params when you need that.

Poll [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) until `passedKycType` reaches the tier you need, or subscribe to the `kyc` event on [KYC and auth webhooks](/server-to-server/webhooks/kyc-and-auth-webhooks.md) instead of polling. Watch `passedKycType` rather than `currentKycStatus === "approved"`: some approved records carry the status `approved-legacy`.

### When it is rejected

| Response                                                             | What happened                                                                                                                                                                                                                                 |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `currentKycStatus: "rejected"`                                       | The provider declined it; the reason is in `currentKycStatusDescription`. The user may try again while `reachedKycLimit` is `false`.                                                                                                          |
| `400 User already passed basic KYC`                                  | The user already holds a basic pass and you submitted another basic document. Submit an advanced one.                                                                                                                                         |
| `400 User already passed advanced KYC`                               | Advanced is the top tier and it is global — there is nothing left to submit. Note this also blocks the Nigerian BVN: an advanced-verified user with `isNgBvnBlocked: true` cannot put a BVN on file themselves and has to go through support. |
| `400 This document is not supported in the selected country`         | The `documentId` belongs to another country.                                                                                                                                                                                                  |
| `400 KYC is not available in the selected country`                   | `kycCountryIsoCode` is a country we do not verify in.                                                                                                                                                                                         |
| `400 Basic verification is not available in the selected country...` | A basic document with a `kycCountryIsoCode` that is not the order country.                                                                                                                                                                    |
| `400 KYC is not required for this merchant.`                         | KYC is switched off for your account. Skip the flow.                                                                                                                                                                                          |

{% hint style="warning" %}
**`reachedKycLimit` is a lifetime cap, not a queue.** It turns `true` once the user has three submissions on file that are not `invalid` — and an approved or rejected record keeps its slot. Waiting changes nothing; only support, voiding a record, frees one. A user who fails twice and succeeds once is at the cap for good, so do not tell them to wait and retry.
{% endhint %}


---

# 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.fonbnk.com/server-to-server/api-endpoints/submit-user-kyc.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.
