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

# Submit KYC Documents

> Submit pre-verified KYC documents for a cardholder via Shared KYC. Available only for businesses with Shared KYC enabled. POST /cardholders/{id}/kyc.

## Overview

Submit identity documents for a cardholder on their behalf using **Shared KYC**. This endpoint is only available to businesses that have completed FYATU's Shared KYC onboarding process.

The endpoint responds immediately with `kycStatus: PENDING`. Documents are processed in the background and a `cardholder.kyc_approved` webhook is dispatched when complete.

<Warning>
  This endpoint returns `403 SHARED_KYC_NOT_ENABLED` if your business does not have Shared KYC enabled. To apply, reach out to FYATU through your dedicated Slack channel. Enabling Shared KYC requires a due diligence review and the use of a recognised KYC provider (e.g. Sumsub, Persona, Onfido).
</Warning>

## Endpoint

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

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

## Path Parameters

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

## Request Fields

| Field          | Type   | Required | Description                                    |
| -------------- | ------ | -------- | ---------------------------------------------- |
| `idFrontUrl`   | string | Yes      | URL to the front image of the ID document      |
| `selfieUrl`    | string | Yes      | URL to a selfie photo of the cardholder        |
| `idBackUrl`    | string | No       | URL to the back image of the ID document       |
| `documentType` | string | No       | `PASSPORT`, `NATIONAL_ID`, or `DRIVER_LICENSE` |

## Image Requirements

* **Format**: JPEG, PNG, GIF, or WebP
* **Maximum size**: 5MB per image
* **Accessibility**: URLs must be publicly accessible

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant App as Your App
    participant FYATU as FYATU API

    App->>FYATU: POST /cardholders/{id}/kyc (document URLs)
    FYATU-->>App: { kycStatus: "PENDING" } (immediate)
    FYATU->>FYATU: Process documents (background)
    FYATU->>App: Webhook: cardholder.kyc_approved
```

1. **Your app** sends document URLs to this endpoint
2. **FYATU** responds immediately with `PENDING` status
3. FYATU processes the documents in the background
4. On success, `kycStatus` is set to `ACCEPTED` and a `cardholder.kyc_approved` webhook is dispatched with the final document URLs
5. On failure, `kycStatus` is set to `REJECTED` — you can re-submit

## Prerequisites

* Business must have **Shared KYC enabled** (contact FYATU via your dedicated Slack channel)
* Cardholder `kycStatus` must be `UNSUBMITTED` or `REJECTED`
* All image URLs must be publicly accessible

## Example Usage

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

  $data = [
      'idFrontUrl' => 'https://storage.example.com/docs/id-front.jpg',
      'idBackUrl'  => 'https://storage.example.com/docs/id-back.jpg',
      'selfieUrl'  => 'https://storage.example.com/docs/selfie.jpg',
      'documentType' => 'NATIONAL_ID',
  ];

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

  $result = json_decode($response, true);
  echo "KYC status: " . $result['data']['kycStatus'] . "\n";
  // Output: KYC status: PENDING
  ```

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

  const data = {
    idFrontUrl: 'https://storage.example.com/docs/id-front.jpg',
    idBackUrl:  'https://storage.example.com/docs/id-back.jpg',
    selfieUrl:  'https://storage.example.com/docs/selfie.jpg',
    documentType: 'NATIONAL_ID',
  };

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

  const result = await response.json();
  console.log('KYC status:', result.data.kycStatus);
  // Output: KYC status: PENDING
  ```
</CodeGroup>

## Example Response

### Success (200)

```json theme={null}
{
  "success": true,
  "status": 200,
  "message": "KYC documents received and are being processed",
  "data": {
    "id": "CH1a2b3c4d5e6f",
    "kycStatus": "PENDING"
  },
  "meta": {
    "requestId": "req_kyc123abc",
    "timestamp": "2026-01-08T17:00:00+00:00"
  }
}
```

The final approved state arrives via webhook once background processing completes:

```json theme={null}
{
  "event": "cardholder.kyc_approved",
  "data": {
    "id": "CH1a2b3c4d5e6f",
    "kycStatus": "ACCEPTED",
    "document": {
      "type": "NATIONAL_ID"
    },
    "kyc": {
      "idFrontUrl": "https://cdn.fyatu.com/kyc/CH1a2b3c4d5e6f/front_1234567890.jpg",
      "idBackUrl":  "https://cdn.fyatu.com/kyc/CH1a2b3c4d5e6f/back_1234567890.jpg",
      "selfieUrl":  "https://cdn.fyatu.com/kyc/CH1a2b3c4d5e6f/selfie_1234567890.jpg"
    }
  }
}
```

## Error Responses

### Shared KYC Not Enabled (403)

```json theme={null}
{
  "success": false,
  "status": 403,
  "message": "Shared KYC is not enabled for this business",
  "error": { "code": "SHARED_KYC_NOT_ENABLED" }
}
```

### Already Accepted (409)

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

### Already Processing (409)

```json theme={null}
{
  "success": false,
  "status": 409,
  "message": "KYC documents are already being processed",
  "error": { "code": "KYC_PENDING" }
}
```

### Validation Error (400)

```json theme={null}
{
  "success": false,
  "status": 400,
  "message": "Validation failed",
  "error": {
    "code": "VALIDATION_ERROR",
    "details": [
      { "field": "selfieUrl", "message": "The selfieUrl field is required." }
    ]
  }
}
```

<Tip>
  You can also submit KYC documents at cardholder creation time by including a `kyc` object in the `POST /cardholders` request body. See the [Create Cardholder](/v3/api-reference/cardholders/create) endpoint for details.
</Tip>


## OpenAPI

````yaml v3/openapi.json POST /cardholders/{id}/kyc
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:
    post:
      tags:
        - Cardholders
      summary: Submit KYC Documents
      description: >-
        Submit KYC documents for a cardholder on their behalf. Documents are
        sent to the verification provider for automated identity checks (face
        match, liveness, document authenticity). The result is delivered via
        webhook.
      operationId: submitCardholderKyc
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Unique cardholder identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CardholderKycRequest'
            example:
              documentType: PASSPORT
              documentNumber: AB123456
              idFrontUrl: https://storage.example.com/documents/id_front.jpg
              idBackUrl: https://storage.example.com/documents/id_back.jpg
              selfieUrl: https://storage.example.com/documents/selfie.jpg
      responses:
        '200':
          description: KYC documents submitted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  status:
                    type: integer
                    example: 200
                  message:
                    type: string
                    example: KYC documents submitted successfully
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: ch_1a2b3c4d5e6f7890abcdef1234567890
                      kycStatus:
                        type: string
                        example: SUBMITTED
                      message:
                        type: string
                        example: >-
                          KYC documents submitted successfully. Verification is
                          pending.
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Conflict - KYC already verified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                status: 409
                message: KYC is already accepted for this cardholder
                error:
                  code: KYC_ALREADY_ACCEPTED
      security:
        - BearerAuth: []
components:
  schemas:
    CardholderKycRequest:
      type: object
      required:
        - documentType
        - documentNumber
        - idFrontUrl
      properties:
        documentType:
          type: string
          enum:
            - PASSPORT
            - NATIONAL_ID
            - DRIVER_LICENSE
          description: Type of identification document
        documentNumber:
          type: string
          minLength: 3
          maxLength: 50
          description: Document identification number
        idFrontUrl:
          type: string
          format: uri
          maxLength: 500
          description: URL to the front image of the ID document
        idBackUrl:
          type: string
          format: uri
          maxLength: 500
          description: URL to the back image of the ID document (optional for passports)
        selfieUrl:
          type: string
          format: uri
          maxLength: 500
          description: URL to a selfie photo holding the ID document
    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
  responses:
    ValidationError:
      description: Validation failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            status: 400
            message: Validation failed
            error:
              code: VALIDATION_ERROR
              details:
                - field: currency
                  message: Currency is required
            meta:
              requestId: req_abc123
              timestamp: '2026-01-05T10:30:00+00:00'
    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'
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT access token obtained from /auth/token

````