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

# Initiate KYC Verification

> Start identity verification for a cardholder via automated ID + liveness check. Returns a verification URL. POST /cardholders/{id}/kyc/session.

## Overview

Initiate an optional automated KYC (Know Your Customer) verification session for a cardholder. This creates a secure verification session where the cardholder completes identity document capture and liveness verification.

KYC verification is **not required** for card issuance — you can issue cards to cardholders without completing KYC. Use this endpoint when you need to verify a cardholder's identity for compliance or enhanced trust.

This is the **self-service** KYC path where the cardholder completes verification themselves. If you already have the cardholder's ID documents and want to submit them on their behalf, use [Submit KYC Documents](/v3/api-reference/cardholders/kyc) instead.

The verification result is delivered asynchronously via webhook (`cardholder.kyc_approved` or `cardholder.kyc_rejected`).

## Endpoint

```
POST /api/v3/cardholders/{cardholderId}/kyc/session
```

**Scope required:** `cardholders:write`

## Path Parameters

| Parameter      | Type   | Required | Description                  |
| -------------- | ------ | -------- | ---------------------------- |
| `cardholderId` | string | Yes      | Unique cardholder identifier |

## Request Body

No request body required.

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant App as Your App
    participant FYATU as FYATU API
    participant Didit as Verification Provider
    participant User as Cardholder

    App->>FYATU: POST /cardholders/{id}/kyc/session
    FYATU-->>App: { verificationUrl, sessionId }
    App->>User: Redirect to verificationUrl
    User->>Didit: Complete ID scan + liveness check
    Didit->>FYATU: Verification result
    FYATU->>App: Webhook: cardholder.kyc_approved or cardholder.kyc_rejected
```

1. **Your app** calls this endpoint to get a verification URL
2. **Redirect** the cardholder to the `verificationUrl`
3. The cardholder **completes** ID document capture and liveness verification
4. **FYATU sends a webhook** to your app with the result (`cardholder.kyc_approved` or `cardholder.kyc_rejected`)

## Verification Fee

A fee is charged per successful verification, based on your plan:

| Plan       | Fee per verification |
| ---------- | -------------------- |
| Startup    | \$1.20               |
| Enterprise | \$0.80               |
| Premium    | \$0.40               |

* The fee is **not charged upfront** - no wallet hold or deduction when initiating verification
* **Added to your invoice** only when verification is approved (successful)
* **Not charged** when verification is declined, abandoned, or expires
* The fee appears as a line item on your next monthly invoice

## Prerequisites

* Cardholder `kycStatus` must be `UNSUBMITTED` or `REJECTED`

## Example Usage

<CodeGroup>
  ```php PHP theme={null}
  <?php
  $cardholderId = 'CH1a2b3c4d5e6f';

  $response = file_get_contents(
      "https://api.fyatu.com/api/v3/cardholders/{$cardholderId}/kyc/session",
      false,
      stream_context_create([
          'http' => [
              'method' => 'POST',
              'header' => [
                  'Authorization: Bearer ' . $accessToken,
                  'Content-Type: application/json'
              ]
          ]
      ])
  );

  $result = json_decode($response, true);

  // Redirect cardholder to the verification URL
  $verificationUrl = $result['data']['verificationUrl'];
  echo "Redirect cardholder to: {$verificationUrl}\n";
  ```

  ```javascript Node.js theme={null}
  const cardholderId = 'CH1a2b3c4d5e6f';

  const response = await fetch(
    `https://api.fyatu.com/api/v3/cardholders/${cardholderId}/kyc/session`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json'
      }
    }
  );

  const result = await response.json();

  // Redirect cardholder to the verification URL
  const { verificationUrl } = result.data;
  console.log('Redirect cardholder to:', verificationUrl);
  ```

  ```python Python theme={null}
  import requests

  cardholder_id = 'CH1a2b3c4d5e6f'

  response = requests.post(
      f'https://api.fyatu.com/api/v3/cardholders/{cardholder_id}/kyc/session',
      headers={
          'Authorization': f'Bearer {access_token}',
          'Content-Type': 'application/json'
      }
  )

  result = response.json()
  verification_url = result['data']['verificationUrl']
  print(f'Redirect cardholder to: {verification_url}')
  ```
</CodeGroup>

## Example Response

### Success (201)

```json theme={null}
{
  "success": true,
  "status": 201,
  "message": "KYC verification session created",
  "data": {
    "cardholderId": "CH1a2b3c4d5e6f",
    "sessionId": "ses_abc123def456",
    "verificationUrl": "https://verify.didit.me/session/ses_abc123def456",
    "kycStatus": "PENDING",
    "fee": 0.60
  },
  "meta": {
    "requestId": "req_kyc789xyz",
    "timestamp": "2026-04-02T10:30:00+00:00"
  }
}
```

### Session Already In Progress (200)

If a verification session is already active, the existing session is returned:

```json theme={null}
{
  "success": true,
  "status": 200,
  "message": "KYC session already in progress",
  "data": {
    "cardholderId": "CH1a2b3c4d5e6f",
    "sessionId": "ses_abc123def456",
    "verificationUrl": "https://verify.didit.me/session/ses_abc123def456",
    "kycStatus": "PENDING"
  },
  "meta": {
    "requestId": "req_kyc790xyz",
    "timestamp": "2026-04-02T10:35:00+00:00"
  }
}
```

## KYC Status Flow

```
UNSUBMITTED ──> PENDING (session initiated) ──> ACCEPTED (verification approved)
      ^                 |
      |                 ├──> REJECTED (verification failed) ──> UNSUBMITTED (can retry)
      |                 |
      |                 └──> UNSUBMITTED (session abandoned/expired, can retry)
      |
      └── REJECTED ──> PENDING (new session initiated)
```

| Status        | Description              | Can Initiate Session          |
| ------------- | ------------------------ | ----------------------------- |
| `UNSUBMITTED` | No verification started  | Yes                           |
| `PENDING`     | Verification in progress | No (returns existing session) |
| `ACCEPTED`    | Verification approved    | No                            |
| `REJECTED`    | Verification failed      | Yes (retry allowed)           |

## Webhook Events

After the cardholder completes (or abandons) verification, you'll receive one of these webhooks:

### `cardholder.kyc_approved`

```json theme={null}
{
  "event": "cardholder.kyc_approved",
  "data": {
    "cardholderId": "CH1a2b3c4d5e6f",
    "firstName": "John",
    "lastName": "Smith",
    "kycStatus": "ACCEPTED",
    "documentType": "PASSPORT"
  },
  "timestamp": "2026-04-02T10:45:00+00:00"
}
```

### `cardholder.kyc_rejected`

```json theme={null}
{
  "event": "cardholder.kyc_rejected",
  "data": {
    "cardholderId": "CH1a2b3c4d5e6f",
    "firstName": "John",
    "lastName": "Smith",
    "kycStatus": "REJECTED",
    "reason": "Document expired"
  },
  "timestamp": "2026-04-02T10:45:00+00:00"
}
```

## Error Responses

### Already Verified (409)

```json theme={null}
{
  "success": false,
  "status": 409,
  "message": "KYC has already been verified for this cardholder",
  "error": { "code": "CONFLICT" }
}
```

### Invalid State (409)

```json theme={null}
{
  "success": false,
  "status": 409,
  "message": "KYC cannot be initiated in current state",
  "error": { "code": "KYC_INVALID_STATE" }
}
```

### Provider Error (503)

```json theme={null}
{
  "success": false,
  "status": 503,
  "message": "Failed to create verification session. Please try again.",
  "error": { "code": "PROVIDER_ERROR" }
}
```

<Tip>
  Store the `verificationUrl` and provide it to the cardholder. If the cardholder doesn't complete verification, you can call this endpoint again to get a new session after the previous one expires.
</Tip>

<Warning>
  The verification fee is only charged on successful verification and added to your next monthly invoice. If the cardholder abandons the session or verification fails, no fee is charged.
</Warning>


## OpenAPI

````yaml v3/openapi.json POST /cardholders/{id}/kyc/session
openapi: 3.1.0
info:
  title: FYATU API v3
  description: >-
    FYATU API v3 with JWT authentication for Collections, Payouts, and Card
    Issuing.
  version: 3.0.0
  contact:
    name: FYATU Support
    url: https://fyatu.com
    email: support@fyatu.com
servers:
  - url: https://api.fyatu.com/api/v3
    description: Production
security: []
tags:
  - name: Authentication
    description: JWT token management endpoints
  - name: Account
    description: Business account, wallet, and address management
  - name: Collections
    description: Accept payments from customers via checkout sessions
  - name: Refunds
    description: Issue refunds for completed collections
  - name: Payouts
    description: Send money to Fyatu account holders
  - name: Cardholders
    description: Cardholder management for card issuing programs
  - name: Cards
    description: Issue, fund, freeze, and manage virtual cards
  - name: Webhooks
    description: Webhook configuration and management
paths:
  /cardholders/{id}/kyc/session:
    post:
      tags:
        - Cardholders
      summary: Initiate KYC Verification
      description: >-
        Initiate an automated KYC verification session for a cardholder. Returns
        a verification URL where the cardholder completes ID document capture
        and liveness check. Result is delivered asynchronously via webhook
        (`cardholder.kyc_approved` or `cardholder.kyc_rejected`). A $0.60 fee is
        held from your business wallet and captured only on successful
        verification.
      operationId: initiateCardholderKycSession
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Unique cardholder identifier
      responses:
        '200':
          description: An active session already exists; the existing session is returned
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  status:
                    type: integer
                    example: 200
                  message:
                    type: string
                    example: KYC session already in progress
                  data:
                    type: object
                    properties:
                      cardholderId:
                        type: string
                        example: ch_1a2b3c4d5e6f7890abcdef1234567890
                      sessionId:
                        type: string
                        example: ses_abc123def456
                      verificationUrl:
                        type: string
                        format: uri
                        example: https://verify.didit.me/session/ses_abc123def456
                      kycStatus:
                        type: string
                        example: PENDING
        '201':
          description: KYC verification session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  status:
                    type: integer
                    example: 201
                  message:
                    type: string
                    example: KYC verification session created
                  data:
                    type: object
                    properties:
                      cardholderId:
                        type: string
                        example: ch_1a2b3c4d5e6f7890abcdef1234567890
                      sessionId:
                        type: string
                        example: ses_abc123def456
                      verificationUrl:
                        type: string
                        format: uri
                        example: https://verify.didit.me/session/ses_abc123def456
                      kycStatus:
                        type: string
                        example: PENDING
                      fee:
                        type: number
                        example: 0.6
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Insufficient business wallet balance to cover the verification fee
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                status: 402
                message: Insufficient balance for KYC verification fee ($0.60)
                error:
                  code: INSUFFICIENT_BALANCE
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Conflict — KYC already verified or invalid state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                status: 409
                message: KYC has already been verified for this cardholder
                error:
                  code: CONFLICT
        '503':
          description: Verification provider unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                status: 503
                message: Failed to create verification session. Please try again.
                error:
                  code: PROVIDER_ERROR
      security:
        - BearerAuth: []
components:
  responses:
    Unauthorized:
      description: Authentication required or token invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            status: 401
            message: Unable to identify business
            error:
              code: AUTH_TOKEN_INVALID
            meta:
              requestId: req_abc123
              timestamp: '2026-01-05T10:30:00+00:00'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            status: 404
            message: Wallet not found
            error:
              code: RESOURCE_NOT_FOUND
            meta:
              requestId: req_abc123
              timestamp: '2026-01-05T10:30:00+00:00'
  schemas:
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        status:
          type: integer
          example: 401
        message:
          type: string
          example: Invalid credentials
        error:
          $ref: '#/components/schemas/Error'
        meta:
          $ref: '#/components/schemas/Meta'
    Error:
      type: object
      properties:
        code:
          type: string
          description: Error code for programmatic handling
          example: AUTH_INVALID_CREDENTIALS
        details:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'
          description: Validation error details (for VALIDATION_ERROR)
    Meta:
      type: object
      properties:
        requestId:
          type: string
          description: Unique request ID for tracking
          example: req_abc123def456
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp of the response
    ValidationError:
      type: object
      properties:
        field:
          type: string
          description: Field that failed validation
          example: appId
        message:
          type: string
          description: Validation error message
          example: AppId is required
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT access token obtained from /auth/token

````