> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kemycard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# KYC Flow

> Verify your customer's identity: from basic to verified

KYC (Know Your Customer) verifies a customer's identity using an official document (passport, ID card, driver's license). Once approved, the customer upgrades from `basic` to `verified`.

The KYC API has two endpoints: one to get the verification link, one to read the result.

## Flow

```
Customer (BASIC)
    │
    ▼
POST /kyc/{customerCode}/link     ← returns the verification link
    │
    ▼
Customer completes verification
    │
    ├── approved  → Customer becomes VERIFIED
    ├── rejected / expired → Customer stays BASIC, request a new link
    └── resubmission_requested → Request a new link, customer retries
```

## Step 1: Get the verification link

```bash theme={null}
curl -X POST https://api.kemycard.com/v1/kyc/a1b2c3d4-e5f6-7890-abcd-ef1234567890/link \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"callback_url": "https://yourapp.com/kyc/done", "lang": "en"}'
```

```json theme={null}
{
  "success": true,
  "data": {
    "customer_code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "kyc_status": "pending",
    "verification_url": "https://verify.kemycard.com/v/abc123",
    "expires_at": "2026-10-06T10:00:00+00:00",
    "reused": false
  }
}
```

The body is optional. `callback_url` is where your customer lands after the verification, `lang` is the language of the verification screen.

<Tip>
  Always send a `callback_url` if you want your customer to come back to your application once the verification is done. Without it, your customer is not redirected back to you: rely on the `kyc.*` webhooks or the status endpoint to know the result.
</Tip>

### You can call it as many times as you need

| Situation | What you get |
| - | - |
| First call | A new link (`201`, `reused: false`) |
| Link still valid | The same link (`200`, `reused: true`) |
| Link expired | A new link |
| Verification rejected or resubmission requested | A new link |
| `"force": true` in the body | Always a new link |

The call is refused when the KYC is already approved, or when the customer is suspended or closed.

### The customer profile must be complete

A link can only be generated for a customer whose profile is complete: `first_name`, `last_name`, `email`, `phone_country_code`, `phone`, `address`, `city`, `state`, `postal_code` and `country`. These fields are required when you create a customer.

If a field is missing (for example `phone_country_code` on a customer created before it became required), the call returns a `422`:

```json theme={null}
{
  "code": "VALIDATION_ERROR",
  "message": "Customer profile is incomplete. Update the customer with the missing fields before requesting a KYC link.",
  "details": { "missing_fields": ["phone_country_code"] }
}
```

Update the customer with `PATCH /customers/{code}`, then request the link again.

## Step 2: Customer completes verification

Redirect your customer to the `verification_url`. They take a picture of their document and a selfie.

## Step 3: Get the result

### Via webhook (recommended)

| Event | Meaning |
| - | - |
| `kyc.approved` | Identity verified — customer is now `verified` |
| `kyc.rejected` | Verification declined or abandoned |
| `kyc.expired` | The verification session expired before completion |
| `kyc.pending_review` | The verification is under manual review |
| `kyc.resubmission_requested` | Documents unclear — request a new link and let the customer retry |

### Via the status endpoint

```bash theme={null}
curl -X GET https://api.kemycard.com/v1/kyc/a1b2c3d4-e5f6-7890-abcd-ef1234567890/status \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

```json theme={null}
{
  "success": true,
  "data": {
    "customer_code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "kyc_status": "approved",
    "verification_level": "verified",
    "verification_url": null,
    "link_expires_at": null,
    "rejection_reason": null,
    "approved_at": "2026-10-05T10:12:00+00:00"
  }
}
```

## Declared identity vs verified identity

When the KYC is approved, the identity read on the document is stored next to the one you declared. Both are returned by the customer endpoints and by the `kyc.approved` webhook:

```json theme={null}
{
  "first_name": "Jon",
  "last_name": "Doe",
  "date_of_birth": "1990-05-15",
  "verified_identity": {
    "first_name": "John",
    "last_name": "Doe",
    "date_of_birth": "1990-05-15"
  },
  "identity_mismatch": true
}
```

| Field | Description |
| - | - |
| `verified_identity` | First name, last name and date of birth read on the document. `null` until the KYC is approved |
| `identity_mismatch` | `true` when the verified identity differs from the declared one, `false` when they match, `null` until the KYC is approved |

Letter case, accents, hyphens and word order are ignored when comparing names.

### What happens on a mismatch

When the KYC is approved with `identity_mismatch: true`:

1. The KYC stays approved and the customer is `verified`, but its `status` becomes **`restricted`** with `restriction_reason: identity_mismatch`. A restricted customer cannot be used.
2. You receive `kyc.approved`, then `kyc.identity_mismatch` with both identities.
3. You call the synchronize endpoint. Your declared identity is replaced by the verified one, the customer is `active` again and `restriction_reason` is back to `null`.

```bash theme={null}
curl -X POST https://api.kemycard.com/v1/kyc/a1b2c3d4-e5f6-7890-abcd-ef1234567890/synchronize \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

```json theme={null}
{
  "success": true,
  "data": {
    "customer_code": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "first_name": "John",
    "last_name": "Doe",
    "date_of_birth": "1990-05-15",
    "verified_identity": {
      "first_name": "John",
      "last_name": "Doe",
      "date_of_birth": "1990-05-15"
    },
    "identity_mismatch": false,
    "kyc_status": "approved",
    "verification_level": "verified",
    "status": "active",
    "restriction_reason": null
  }
}
```

No new verification is needed and nothing is billed.

<Warning>
  A `restricted` customer cannot be reactivated with `POST /customers/{code}/activate`, and its `first_name`, `last_name` and `date_of_birth` cannot be changed with `PATCH /customers/{code}`. Synchronizing the identity is the only way to lift the restriction.
</Warning>

### Customer statuses

| Status | Set by | Reversible |
| - | - | - |
| `active` | — | — |
| `suspended` | You (suspend endpoint) | Yes, by you (activate endpoint) |
| `restricted` | KemyCard | Yes, only by the action given in `restriction_reason` |
| `closed` | KemyCard | No, permanent |

## Pricing

A verification is billed **2 USD, only when it is approved**. The amount is debited from your `others` ops balance and triggers a `balance.debited` webhook with `reason: kyc_verification`.

Getting a link is free, but your `others` balance must cover the price of one verification, otherwise the call returns `INSUFFICIENT_BALANCE`.

## KYC status values

| Status | Description |
| - | - |
| `not_started` | No verification link has been requested yet |
| `pending` | A link has been issued, verification in progress |
| `approved` | Identity verified — customer upgrades to `verified` |
| `rejected` | Verification declined, abandoned or expired — request a new link to retry |
| `resubmission_requested` | Insufficient documents, new submission required |

<Warning>
  If a `verified` customer's identity fields are modified (`first_name`, `last_name`, `date_of_birth`), they are automatically **demoted back to `basic`** and must redo KYC.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.