# KYC flow

Most flows require validating a user’s Know Your Customer (KYC) level before creating an order. The high-level steps are:\ <br>

1. Call [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) with a user's email and a country.
2. If <mark style="color:yellow;">`requiredKycType`</mark> is non-null and higher than <mark style="color:yellow;">`passedKycType`</mark>, the user has already crossed an aggregate threshold and must upgrade KYC before further orders. Skip to step 5.
3. Otherwise, inspect <mark style="color:yellow;">`kycSettings`</mark> to determine whether the order you're about to create triggers a per-order rule:
   * Match settings where <mark style="color:yellow;">`operationType`</mark> and <mark style="color:yellow;">`currencyType`</mark> align with the order side you care about.
   * For per-order entries (<mark style="color:yellow;">`min`</mark>/<mark style="color:yellow;">`max`</mark> set), check whether the order's USD value falls in <mark style="color:yellow;">`[min, max)`</mark>.
   * Take the highest <mark style="color:yellow;">`type`</mark> among all matching entries.
4. If <mark style="color:yellow;">`passedKycType`</mark> is already at or above the required tier, proceed to quoting and order creation.
5. Otherwise, pick the document from <mark style="color:yellow;">`kycDocuments`</mark> whose <mark style="color:yellow;">`type`</mark> matches the required tier, collect <mark style="color:yellow;">`requiredFields`</mark> from the user, and submit via **Submit user KYC**. The response is the same shape as Get user KYC state.
6. Poll **Get user KYC state** until <mark style="color:yellow;">`currentKycStatus === "approved"`</mark> and <mark style="color:yellow;">`passedKycType`</mark> reaches the required tier. (For Nigerian advanced KYC, <mark style="color:yellow;">`currentKycPhase`</mark> transitions from <mark style="color:yellow;">`bvn_check`</mark> to <mark style="color:yellow;">`images_check`</mark> internally — no extra action needed.)

\
**Note on amount-based pre-checks:** <mark style="color:yellow;">`requiredKycType`</mark> reflects only the user's lifetime *successful* order history (aggregate rules). It does not account for the size of the next order. To pre-check whether a specific upcoming order will trip a per-order rule, evaluate <mark style="color:yellow;">`kycSettings`</mark> yourself as described in step 3.

**Tip:** In the sample below, a per-order rule requires <mark style="color:yellow;">`advanced`</mark> KYC for any single crypto payout of $100 or more, and an aggregate rule requires <mark style="color:yellow;">`advanced`</mark> once the user's lifetime successful crypto payouts exceed $1,000.

### Response fields

* **`passedKycType`** (<mark style="color:yellow;">`"basic" | "advanced" | null`</mark>) — the highest KYC tier the user has already passed.
* **`reachedKycLimit`** (<mark style="color:yellow;">`boolean`</mark>) — <mark style="color:yellow;">`true`</mark> when the user has too many pending KYC submissions and cannot initiate another until existing ones resolve.
* **`requiredKycType`** (<mark style="color:yellow;">`"basic" | "advanced" | null`</mark>) — server-computed required tier based on the user's lifetime successful order history (aggregate rules only). If non-null and higher than <mark style="color:yellow;">`passedKycType`</mark>, the user must upgrade KYC. <mark style="color:yellow;">`null`</mark> means no aggregate rule is forcing an upgrade — a per-order rule in <mark style="color:yellow;">`kycSettings`</mark> may still apply to the next order.
* **`currentKycType`** (<mark style="color:yellow;">`"basic" | "advanced" | undefined`</mark>) — the tier of the user's latest in-flight KYC submission.
* **`currentKycStatus`** (<mark style="color:yellow;">`"initiated" | "approved" | "rejected" | "invalid" | null`</mark>) — status of the latest KYC submission.
* **`currentKycStatusDescription`** (<mark style="color:yellow;">`string | null`</mark>) — human-readable reason for the current status.
* **`currentKycPhase`** (<mark style="color:yellow;">`"bvn_check" | "images_check" | undefined`</mark>) — set only for Nigerian advanced KYC. Internal sub-step of an advanced verification. Merchants submit all fields (including images) in a single call; phases progress internally. Use only for progress UI.
* **`kycDocuments`** — list of documents available for the country, each with <mark style="color:yellow;">`_id`</mark>, <mark style="color:yellow;">`type`</mark>, <mark style="color:yellow;">`title`</mark>, <mark style="color:yellow;">`value`</mark>, and a <mark style="color:yellow;">`requiredFields`</mark> array.
* **`kycSettings`** — list of KYC rules for the country. Two variants coexist:
  * **Per-order** — <mark style="color:yellow;">`min`</mark> and <mark style="color:yellow;">`max`</mark> are set. The rule fires when the order's USD value on the matching <mark style="color:yellow;">`operationType`</mark> + <mark style="color:yellow;">`currencyType`</mark> side is in <mark style="color:yellow;">`[min, max)`</mark>. <mark style="color:yellow;">`max`</mark> may be <mark style="color:yellow;">`"Infinity"`</mark>.
  * **Aggregate** — <mark style="color:yellow;">`maxOrdersCount`</mark> and/or <mark style="color:yellow;">`maxAmountUsd`</mark> are set. The rule fires once the user's lifetime successful order count or USD volume in that <mark style="color:yellow;">`(operationType, currencyType)`</mark> bucket reaches the threshold. This is what `requiredKycType` reflects.
  * All settings carry <mark style="color:yellow;">`operationType`</mark>, <mark style="color:yellow;">`currencyType`</mark>, and <mark style="color:yellow;">`type`</mark> (required KYC tier). When multiple fire, the highest <mark style="color:yellow;">`type`</mark> wins.

Sample [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) response

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

```json
{
  "passedKycType": null,
  "reachedKycLimit": false,
  "currentKycType": null,
  "currentKycStatus": null,
  "currentKycStatusDescription": null,
  "currentKycPhase": null,
  "requiredKycType": null,
  "kycDocuments": [
    {
      "_id": "67da909b739fc481aa525c43",
      "type": "basic",
      "title": "Voter ID",
      "value": "VOTER_ID",
      "requiredFields": [
        { "key": "first_name", "type": "string", "label": "First Name", "required": true },
        { "key": "last_name",  "type": "string", "label": "Last Name",  "required": true },
        { "key": "dob",        "type": "date",   "label": "Date of birth", "required": true },
        {
          "key": "id_number",
          "type": "string",
          "label": "ID number",
          "required": true,
          "format": "0000000000000000000",
          "regexp": "^[a-zA-Z0-9 ]{9,29}$",
          "regexpFlags": "i"
        }
      ]
    },
    {
      "_id": "67da93c0dfd3a00f3380b857",
      "type": "advanced",
      "title": "Driving License",
      "value": "DRIVERS_LICENSE",
      "requiredFields": [
        { "key": "first_name", "type": "string", "label": "First Name", "required": true },
        { "key": "last_name",  "type": "string", "label": "Last Name",  "required": true },
        { "key": "dob",        "type": "date",   "label": "Date of birth", "required": true },
        { "key": "images",     "type": "smile-identity-images", "label": "Verification images", "required": true }
      ]
    }
  ],
  "kycSettings": [
    { "operationType": "deposit", "currencyType": "crypto", "type": "basic",    "min": 0,   "max": 100 },
    { "operationType": "payout",  "currencyType": "crypto", "type": "advanced", "min": 100, "max": "Infinity" },
    { "operationType": "payout",  "currencyType": "crypto", "type": "advanced", "maxAmountUsd": 1000 }
  ]
}
```

{% endcode %}

The "<mark style="color:yellow;">advanced</mark>" KYC document requires images. Submit them as an array of objects with <mark style="color:yellow;">image\_type\_id</mark> and <mark style="color:yellow;">image</mark> URL. Image URLs must be publicly accessible or contain the image data as a base64-encoded string. The "<mark style="color:yellow;">image\_type\_id</mark>" can accept the following values:

* 0: Selfie
* 1: Document front
* 5: Document back

Sample <mark style="color:yellow;">Submit user KYC</mark> request

```json
{
  "userEmail": "someuser@example.com",
  "countryIsoCode": "NG",
  "documentId": "67da93c0dfd3a00f3380b857",
  "userFields": {
    "first_name": "John",
    "last_name": "Doe",
    "dob": "1990-01-01",
    "images": [
      { "image_type_id": 0, "image": "https://cdn.com/selfie.jpg" },
      { "image_type_id": 1, "image": "https://cdn.com/front.jpg" },
      { "image_type_id": 5, "image": "https://cdn.com/back.jpg" }
    ]
  }
}
```

The Submit response has the same shape as [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md). Continue polling [Get user KYC state](/server-to-server/api-endpoints/get-user-kyc-state.md) until <mark style="color:yellow;">`currentKycStatus`</mark> becomes <mark style="color:yellow;">`approved`</mark> and <mark style="color:yellow;">`passedKycType`</mark> reaches the required tier, then proceed with quoting and order creation.


---

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

```
GET https://docs.fonbnk.com/server-to-server/kyc-flow.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
