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

# Create KYC Validation

> Initiate a KYC verification session for a person entity — in the gu1 KYC API for identity verification flows, with examples for create validation use cases.

## Overview

This endpoint creates a new KYC validation session for a person entity using a configured integration provider. After creating the validation, you'll receive a verification URL that you can share with your customer to complete identity verification.

## Complete Flow Diagram

Full sequence from entity creation to KYC validation (production flow; in sandbox with test document numbers the provider step is skipped and the API returns the mock result and webhooks immediately—see [Sandbox mock data](/en/use-cases/kyc/sandbox-mock-data)):

```mermaid theme={null}
sequenceDiagram
    participant Client as Your Application
    participant API as gu1 API
    participant User as End User
    participant KYC as KYC Provider

    Note over Client,KYC: Step 1: Create Person Entity
    Client->>API: POST /api/entities
    Note right of Client: type, taxId, name, countryCode
    API-->>Client: 201 Created { entityId }<br/>or 409 if taxId already exists

    Note over Client,KYC: Step 2: Create KYC Validation
    Client->>API: POST /api/kyc/validations
    Note right of Client: entityId, integrationCode
    API->>KYC: Initialize verification session
    KYC-->>API: Session created
    API-->>Client: 201 Created<br/>{ id, providerSessionUrl, status: "pending" }

    Note over Client,KYC: Step 3: User Completes Verification
    Client->>User: Send providerSessionUrl
    User->>KYC: Opens URL, uploads document, selfie
    KYC->>KYC: Document + liveness + face match
    KYC->>API: Result (provider webhook or sync)
    API->>API: Update validation status

    Note over Client,KYC: Step 4: Webhook to Client
    API->>Client: POST your webhook URL
    Note right of API: event: kyc.validation_approved<br/>or kyc.validation_rejected<br/>payload: full validation

    Note over Client,KYC: Step 5: Optional – Query Status
    Client->>API: GET /api/kyc/validations/:id
    API-->>Client: 200 OK { status, ... }
```

<Accordion title="Key Points in the Flow">
  **1. Entity and duplicate taxId**

  * Create the person entity first via `POST /api/entities` (with `countryCode`). If the `taxId` already exists in the organization, the API returns **409** and does not create a duplicate; use the existing entity’s ID.
  * You need an existing `entityId` to create a KYC validation.

  **2. Integration code**

  * `global_gueno_validation_kyc` is the standard code for full KYC verification and works in sandbox with no extra setup.

  **3. Verification URL**

  * In production, the response includes `providerSessionUrl`. Send that URL to your user; they complete the flow on the provider's hosted page (document + selfie). The URL is valid until `expiresAt`.
  * In **sandbox**, if the entity's document number is in the [test list](/en/use-cases/kyc/sandbox-mock-data), no provider session is used: the API returns 201 with `pending` status and **moments later** updates the validation and sends webhooks (e.g. `kyc.validation_approved` or `kyc.validation_rejected`) with no user step. **Important:** You must have a webhook endpoint configured to receive the responses - see [Sandbox mock data](/en/use-cases/kyc/sandbox-mock-data).

  **4. Webhook events**

  * When the validation finishes, the API sends a webhook to your URL. The **event** is one of: `kyc.validation_approved`, `kyc.validation_rejected`, `kyc.validation_abandoned`, `kyc.validation_expired`, `kyc.validation_cancelled` (not a single “completed” event). The payload is the full validation object.
  * You can also poll `GET /api/kyc/validations/:id` for status updates.
</Accordion>

## Prerequisites

Before creating a KYC validation:

1. **Person entity must exist**: Create a person entity using the [Entities API](/api-reference/entities/create)
2. **KYC integration configured**: Your organization must have a KYC integration provider activated (e.g., `global_gueno_validation_kyc`)
3. **Valid API key**: Authenticate with your API key

<Info>
  **Sandbox vs Production**: Sandbox environments do NOT require profile setup or pre-configuration. You can test KYC validations immediately in sandbox with test data. Production environments require:

  * Completed organization onboarding
  * KYC integration provider activated by gu1 team
  * Sufficient credit balance for KYC operations

  To get started in sandbox, simply use your sandbox API key - no additional setup needed.
</Info>

### Mock Data (Sandbox)

In sandbox, when the person entity’s document number (`taxId`) matches one of our **test values**, the API returns an **immediate mock result** (e.g. approved, rejected, cancelled) and sends the corresponding webhooks—no real verification is run. Document format does not matter (e.g. `99.990.001` and `99990001` both work).

For the full list of test document numbers by format (Argentina DNI/CUIT, Brazil CPF/CNPJ), expected outcomes, and example responses, see **[Sandbox mock data](/en/use-cases/kyc/sandbox-mock-data)**.

## Important Behaviors

### Duplicate taxId (POST /entities)

<Warning>
  When you call **POST /api/entities** with a `taxId` that **already exists** in the same organization for the same entity type (person/company), the API **does not** create a second entity. It returns **409 Conflict** with error code `DUPLICATE_TAX_ID` and includes the existing entity’s `id`, `name`, and `type` in the response details.

  **What you should do:**

  1. **Option A – Check first**: Use `GET /api/entities?taxId=12345678` (or the by-tax-id endpoint) before creating. If an entity exists, use its `entityId` for KYC.
  2. **Option B – Handle 409**: If you get 409, read `error.details.existingEntityId` from the response and use that `entityId` for your KYC validation.
  3. **Reuse the same entity**: Use one entity per person/company and create multiple KYC validations on that same `entityId` if you need re-verification or new attempts.

  **Example – Check before create:**

  ```javascript theme={null}
  // Check if entity exists
  const existing = await fetch('https://api.gu1.ai/api/entities?taxId=12345678', {
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
  });
  const data = await existing.json();

  let entityId;
  if (data.data && data.data.length > 0) {
    entityId = data.data[0].id; // Use existing entity
  } else {
    const createRes = await fetch('https://api.gu1.ai/api/entities', {
      method: 'POST',
      headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
      body: JSON.stringify({ type: 'person', taxId: '12345678', name: 'John Doe', countryCode: 'AR' })
    });
    const created = await createRes.json();
    entityId = created.entity?.id ?? created.data?.id;
  }

  // Create KYC validation with the entityId
  ```
</Warning>

### Multiple KYC Validations per Entity

You **can** create multiple KYC validations for the same entity:

* Each validation gets a unique ID and session
* Only the most recent **approved** validation is marked as `isCurrent: true`
* Use cases: Re-verification, expired validations, failed attempts
* If the entity already has an **open** validation (`pending`, `in_progress`, or `in_review`), create returns **`409 VALIDATION_IN_PROGRESS`** with **`activeValidationId`** — cancel that validation first, then create again

<Note>
  **Additive contract:** Existing clients that only read `error` and `message` are unchanged. `activeValidationId` is optional metadata on the 409 response to skip an extra lookup before cancel.
</Note>

## Request

### Endpoint

```
POST https://api.gu1.ai/api/kyc/validations
```

### Headers

```json theme={null}
{
  "Authorization": "Bearer YOUR_API_KEY",
  "Content-Type": "application/json"
}
```

### Query Parameters (Optional)

<ParamField query="doubleCheckRenaper" type="boolean">
  When `true`, enables RENAPER double-check for **Argentina** entities. On **terminal** statuses the API queries the official registry (data and, when applicable, biometrics) and stores results in metadata. **Only** when **OCR KYC verification** returns status **`approved`** can a cross-check failure automatically set the validation to `rejected`; on `in_review` or `rejected` the check is informational (`enforcementApplied: false`).

  **Type**: `boolean` (e.g. `?doubleCheckRenaper=true`)

  **Requirements**: Entity must be from Argentina (`countryCode === 'AR'`), and the organization must have RENAPER credentials configured in System Settings → Organizations → Config. If credentials are missing, the creation may fail or the double-check will be skipped depending on configuration.
</ParamField>

### Body Parameters

Provide **exactly one** entity identifier:

<ParamField body="entityId" type="string">
  The UUID of the person entity to verify

  **Type**: `string` (uuid)
</ParamField>

<ParamField body="entityExternalId" type="string">
  Your external ID for the entity (`entities.externalId` in Gu1).
</ParamField>

<ParamField body="entityTaxId" type="string">
  Tax or document number. Gu1 resolves it with normalized match on `entities.tax_id`. The entity **must exist** (404 if not found).
</ParamField>

<Info>
  In **sandbox**, `GET /api/entities/by-tax-id/{taxId}` can return a synthetic preview for catalog test numbers (`sandboxMock: true`) without a DB row. **POST /validations** still requires a real persisted entity.
</Info>

<ParamField body="integrationCode" type="string" required>
  The integration provider code for KYC validation

  **Standard Value**: `global_gueno_validation_kyc` (recommended for most use cases)

  **Type**: `string` (min length: 1)

  <Note>
    **What is integrationCode?**

    The `integrationCode` identifies which KYC provider integration to use for verification. Think of it as selecting the verification service.

    **Available Integration Codes:**

    * `global_gueno_validation_kyc` - **Recommended** - Full KYC with document + selfie + face match + liveness
    * Custom codes may be configured for your organization (contact support)

    **How to find your integration code:**

    1. Log in to [gu1 Dashboard](https://app.gu1.ai)
    2. Navigate to Settings → Integrations → KYC Providers
    3. Your active integration code will be listed there

    In **sandbox environments**, `global_gueno_validation_kyc` works immediately with no configuration.
  </Note>
</ParamField>

<ParamField body="doubleCheckRenaper" type="boolean">
  Same as query param `doubleCheckRenaper`. When `true`, enables RENAPER double-check for Argentina. Can be sent in the body or as `?doubleCheckRenaper=true`. If both are present, the query param takes precedence.
</ParamField>

<ParamField body="omitWarnings" type="string[]">
  Optional list of [KYC warning risk codes](/en/use-cases/kyc/warning-risk-codes) (exact strings). Stored on the validation as `metadata.omitWarnings`. After the session completes, if the validation would be **`in_review`**, **`warnings` is non-empty**, and **every** code in `warnings` appears in this list, the API sets status to **`approved`** and keeps all warnings for display and audit. If **any** warning is **not** in `omitWarnings`, status stays **`in_review`**. If `warnings` is empty while status is `in_review`, this rule does **not** auto-approve. Invalid or unknown codes in the request body return **400**. When the rule applies, `metadata.kycOmitWarningsApplied` records the decision timestamp and matched warnings.

  **Non-omittable codes** (e.g. `GUENO_CROSS_ENTITY_DUPLICATED`) are rejected in this field with **400** and always block omit auto-approve even if present in `warnings`.

  **Type**: `string[]` (each element must be an allowlisted code; duplicates are ignored)
</ParamField>

## Response

### Success Response (201 Created)

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "entityId": "123e4567-e89b-12d3-a456-426614174000",
  "organizationId": "org_abc123",
  "sessionId": "session_xyz789",
  "status": "pending",
  "provider": "kyc_provider",
  "providerSessionUrl": "https://verify.example.com/session_xyz789",
  "isCurrent": true,
  "metadata": {
    "customField": "customValue",
    "doubleChecks": { "renaper": true }
  },
  "createdAt": "2025-01-15T10:30:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}
```

When RENAPER double-check is enabled, `metadata.doubleChecks.renaper` is set to `true` at creation. After OCR KYC verification completes and the double-check runs, `metadata.responseDoubleChecks.renaper` is populated (see [RENAPER double-check](#renaper-double-check-argentina) below).

### Response Fields

<ResponseField name="id" type="string">
  Unique identifier for this KYC validation
</ResponseField>

<ResponseField name="entityId" type="string">
  The person entity being verified
</ResponseField>

<ResponseField name="organizationId" type="string">
  Your organization ID
</ResponseField>

<ResponseField name="sessionId" type="string">
  Identity verification session identifier
</ResponseField>

<ResponseField name="status" type="string">
  Current validation status. Possible values:

  * `pending` - Validation created, waiting for customer to start
  * `in_progress` - Customer is completing verification (filling out form)
  * `in_review` - Verification completed, requires manual review from compliance team
  * `approved` - Verification successful
  * `rejected` - Verification failed
  * `expired` - Verification session expired (e.g. after 7 days)
  * `abandoned` - Customer started but didn't complete
  * `cancelled` - Validation manually cancelled
</ResponseField>

<ResponseField name="provider" type="string">
  KYC provider name
</ResponseField>

<ResponseField name="providerSessionUrl" type="string">
  **The verification URL to share with your customer**
</ResponseField>

<ResponseField name="isCurrent" type="boolean">
  Whether this is the current active validation for the entity
</ResponseField>

<ResponseField name="metadata" type="object">
  Custom metadata. When RENAPER double-check is requested, includes `doubleChecks: { renaper: true }`. After approval and double-check execution, may include `responseDoubleChecks.renaper` (see RENAPER double-check section).
</ResponseField>

<ResponseField name="createdAt" type="string">
  Timestamp when validation was created
</ResponseField>

<ResponseField name="updatedAt" type="string">
  Timestamp of last update
</ResponseField>

<Info>
  After approval, media keys appear inside **`decision`**. To download files (images, video), use **`GET /api/kyc/validations/:id/media?key=...`** with `Authorization: Bearer`. Full details: [Get KYC validation media](/en/use-cases/kyc/validation-media).
</Info>

## Example Request

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await fetch('https://api.gu1.ai/api/kyc/validations', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      entityId: '123e4567-e89b-12d3-a456-426614174000',
      integrationCode: 'global_gueno_validation_kyc'
    })
  });

  const validation = await response.json();
  console.log('Verification URL:', validation.providerSessionUrl);
  ```

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

  response = requests.post(
      'https://api.gu1.ai/api/kyc/validations',
      headers={
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json',
      },
      json={
          'entityId': '123e4567-e89b-12d3-a456-426614174000',
          'integrationCode': 'global_gueno_validation_kyc'
      }
  )

  validation = response.json()
  print('Verification URL:', validation['providerSessionUrl'])
  ```

  ```curl cURL theme={null}
  curl -X POST https://api.gu1.ai/api/kyc/validations \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "entityId": "123e4567-e89b-12d3-a456-426614174000",
      "integrationCode": "global_gueno_validation_kyc"
    }'
  ```
</CodeGroup>

## Error Responses

### Entity Not Found (404)

```json theme={null}
{
  "error": "ENTITY_NOT_FOUND",
  "message": "Entity not found"
}
```

### Invalid Entity Type (400)

```json theme={null}
{
  "error": "INVALID_ENTITY_TYPE",
  "message": "KYC validation is only available for person entities"
}
```

### KYC Not Configured (400)

```json theme={null}
{
  "error": "KYC_NOT_CONFIGURED",
  "message": "KYC integration is not configured for this organization. Please contact your administrator."
}
```

### Validation In Progress (409)

When the entity already has an open validation (`pending`, `in_progress`, or `in_review`), Gu1 syncs with the hosted capture session when possible, then rejects a second create:

```json theme={null}
{
  "error": "VALIDATION_IN_PROGRESS",
  "message": "Cannot create new KYC validation. There is already a pending validation for this entity. Please cancel or complete the existing validation first.",
  "activeValidationId": "7618e10c-b1b1-408b-b361-1901077ced73"
}
```

**Recovery:** `409` → `DELETE /api/kyc/validations/{activeValidationId}/cancel` → one new `POST /api/kyc/validations`.

Terminal validations (`approved`, `rejected`, `cancelled`, `expired`, `abandoned`) do **not** block creating a new one.

## RENAPER Double-Check (Argentina)

When `doubleCheckRenaper` is `true` and the entity is from Argentina, on each **terminal** status (`approved`, `rejected`, `in_review`) the API runs a cross-check against the official Argentine registry (RENAPER) when sufficient OCR data exists. Automatic rejection by RENAPER **only** applies when **OCR KYC verification** returned status **`approved`**.

### How to Send It

* **Body**: `{ "entityId": "...", "integrationCode": "...", "doubleCheckRenaper": true }`
* **Query**: `POST /api/kyc/validations?doubleCheckRenaper=true` with the same body (without the field). Query param overrides body if both are present.

### When It Runs

1. Validation is created with `metadata.doubleChecks.renaper: true`.
2. User completes verification; OCR KYC verification returns a **terminal** status.
3. API calls RENAPER (data + biometrics when applicable) and stores `comparisonResults` in metadata.
4. If status is **`approved`** and the cross-check fails → validation becomes **`rejected`**.
5. If status is **`in_review`** or **`rejected`** → results are informational; mismatch codes are **added** to `warnings` without replacing OCR verification warnings.
6. **Manual approve** from `in_review` does **not** re-run RENAPER; the human decision stands on the data already shown in review.

### Where the Result Is Stored

All RENAPER double-check results are stored in **`metadata.responseDoubleChecks.renaper`**. Mismatch **codes** are **added** to **`metadata.warnings`** alongside OCR KYC verification warnings. `errorCode` on the `renaper` object keeps the first failure for compatibility; UIs can list every code from `comparisonResults` and `warnings`.

**Shape of `metadata.responseDoubleChecks.renaper`:**

| Field                | Type    | Description                                                                                   |
| -------------------- | ------- | --------------------------------------------------------------------------------------------- |
| `verified`           | boolean | `true` if the double-check passed.                                                            |
| `matchResult`        | string  | `"match"` \| `"mismatch"` \| `"error"`.                                                       |
| `verifiedAt`         | string  | ISO timestamp when the check was run.                                                         |
| `personalNumber`     | string  | Transaction number from KYC (normalized).                                                     |
| `idTramitePrincipal` | string  | Transaction number from RENAPER.                                                              |
| `renaperData`        | object  | Raw RENAPER API response (see [`renaperData` shape](#renaperdata-shape)).                     |
| `comparisonResults`  | object  | Per-field OCR vs registry comparison (when applicable).                                       |
| `renaperBiometric`   | object  | ABIS biometric result (`validate-dni`): `matchResult`, `score`, `renaperData` (raw response). |
| `errorCode`          | string  | Present when failed; see error codes below.                                                   |
| `error`              | string  | Optional legacy message; prefer using `errorCode` and translating in your UI.                 |

### `renaperData` shape

This is the **unmodified body** returned by the registry via ms-providers (`POST …/provider-records/renaper/data`). Gu1 forwards it as-is in `metadata.responseDoubleChecks.renaper.renaperData`. Field names use **snake\_case**; all fields are optional depending on what RENAPER returns for each lookup.

**Example (successful double-check, `matchResult: "match"`):**

```json theme={null}
{
  "id_tramite_principal": "987654321",
  "id_tramite_tarjeta_reimpresa": "112233445",
  "ejemplar": "A",
  "vencimiento": "2030-05-20",
  "emision": "2015-05-20",
  "apellido": "García",
  "nombres": "Juan",
  "fecha_nacimiento": "1990-01-15",
  "cuil": "20-30123456-9",
  "calle": "Av. Corrientes",
  "numero": "1234",
  "piso": "5",
  "departamento": "B",
  "codigo_postal": "1043",
  "barrio": "San Nicolás",
  "monoblock": "",
  "ciudad": "Ciudad Autónoma de Buenos Aires",
  "municipio": "Comuna 1",
  "provincia": "Buenos Aires",
  "pais": "Argentina",
  "nacionalidad": "Argentina",
  "codigo_fallecido": "",
  "mensaje_fallecido": "",
  "fecha_fallecimiento": "",
  "id_ciudadano": "30123456",
  "codigo": "",
  "mensaje": ""
}
```

**Other common cases:**

| Situation                                                | Typical `renaperData`                                                                     |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Error before registry data (e.g. `RENAPER_DNI_MISSING`)  | `{}`                                                                                      |
| Service unavailable (`RENAPER_VERIFICATION_UNAVAILABLE`) | `{ "error": "…", "code": "SERVICE_UNAVAILABLE" }`                                         |
| Failed cross-check with partial registry payload         | Subset of fields (e.g. `id_tramite_principal`, `apellido`, `nombres`, `fecha_nacimiento`) |

In sandbox, the same mock values appear in [KYC sandbox mock data](/en/use-cases/kyc/sandbox-mock-data#example-response-mock-approved).

### `comparisonResults` shape

Per-field map (`dni`, `tramite`, `name`, `ejemplar`, `dateOfBirth`, `expirationDate`). Each entry may include:

| Field               | Type    | Description                                                                 |
| ------------------- | ------- | --------------------------------------------------------------------------- |
| `compared`          | boolean | `true` if comparison was attempted; `false` if skipped due to missing data. |
| `passed`            | boolean | Outcome when `compared` is `true`.                                          |
| `skipReason`        | string  | `ocr_missing`, `renaper_missing`, or `both_missing` when not compared.      |
| `ocrValue`          | string  | OCR-extracted value (when applicable).                                      |
| `renaperValue`      | string  | Registry value from RENAPER (when applicable).                              |
| `similarityPercent` | number  | For `name` only: Levenshtein similarity (0–100).                            |
| `threshold`         | number  | For `name` only: applied threshold (e.g. 80).                               |
| `errorCode`         | string  | Field failure code (e.g. `RENAPER_DNI_NOT_MATCH`).                          |

For **`ejemplar`**, the OCR value is **`extractedData.ejemplar`** (see [extractedData fields](/en/use-cases/kyc/id-verification#extracteddata-fields)). Comparison runs when both OCR and RENAPER values exist.

**Example — `extractedData` on an approved KYC validation (Argentina):**

```json theme={null}
"extractedData": {
  "documentNumber": "38966181",
  "personalNumber": "00460759387",
  "taxNumber": "20389661814",
  "ejemplar": "C",
  "dateOfBirth": "1995-05-22",
  "expirationDate": "2031-10-17",
  "nationality": "ARG"
}
```

### `renaperBiometric` shape

Nested under `metadata.responseDoubleChecks.renaper.renaperBiometric` when the org has biometric credentials and the KYC session provides a selfie. Gu1 sends **one selfie** to `validate-dni`; RENAPER compares it to the ID photo on file.

| Field                       | Type    | Description                                                                        |
| --------------------------- | ------- | ---------------------------------------------------------------------------------- |
| `verified`                  | boolean | `true` when the ABIS biometric check returns `resultado.match`.                    |
| `matchResult`               | string  | `"match"` \| `"mismatch"` \| `"error"`.                                            |
| `score`                     | number  | Score from the biometric check (when present).                                     |
| `verifiedAt`                | string  | ISO timestamp of the biometric check.                                              |
| `renaperData`               | object  | Raw `validate-dni` response (includes `resultado.match`, `resultado.score`, etc.). |
| `submittedSelfieRef`        | string  | Reference of the selfie sent: storage path (`kyc/...`) or URL (`https://...`).     |
| `submittedSelfieRefKind`    | string  | Format of `submittedSelfieRef`: `s3_key` (internal path) \| `url` (public URL).    |
| `submittedSelfiePickedFrom` | string  | Source in `decision`: `liveness_reference_image` or `face_match_target_image`.     |
| `errorCode`                 | string  | On failure (e.g. `RENAPER_BIOMETRIC_NOT_MATCH`).                                   |
| `skipReason`                | string  | When not run (e.g. selfie unavailable).                                            |

### Full `responseDoubleChecks.renaper` example

```json theme={null}
{
  "verified": true,
  "matchResult": "match",
  "personalNumber": "00123456789",
  "idTramitePrincipal": "987654321",
  "verifiedAt": "2026-06-05T14:30:00.000Z",
  "enforcementApplied": true,
  "renaperData": {
    "id_tramite_principal": "987654321",
    "apellido": "García",
    "nombres": "Juan",
    "fecha_nacimiento": "1990-01-15",
    "ejemplar": "A",
    "vencimiento": "2030-05-20"
  },
  "comparisonResults": {
    "dni": {
      "field": "dni",
      "compared": true,
      "passed": true,
      "ocrValue": "30123456",
      "renaperValue": "30123456"
    },
    "tramite": {
      "field": "tramite",
      "compared": true,
      "passed": true,
      "ocrValue": "00123456789",
      "renaperValue": "987654321"
    },
    "name": {
      "field": "name",
      "compared": true,
      "passed": true,
      "ocrValue": "Juan García",
      "renaperValue": "García Juan",
      "similarityPercent": 92,
      "threshold": 80
    },
    "ejemplar": {
      "field": "ejemplar",
      "compared": true,
      "passed": true,
      "ocrValue": "A",
      "renaperValue": "A"
    },
    "dateOfBirth": {
      "field": "dateOfBirth",
      "compared": true,
      "passed": true,
      "ocrValue": "1990-01-15",
      "renaperValue": "1990-01-15"
    }
  },
  "renaperBiometric": {
    "verified": true,
    "matchResult": "match",
    "score": 0.91,
    "verifiedAt": "2026-06-05T14:30:02.000Z",
    "submittedSelfieRef": "kyc/org-id/entity-id/session-id/selfie.jpg",
    "submittedSelfieRefKind": "s3_key",
    "submittedSelfiePickedFrom": "liveness_reference_image",
    "renaperData": {
      "resultado": {
        "match": true,
        "score": 0.91
      }
    }
  }
}
```

`renaperBiometric` and `comparisonResults` entries may be omitted depending on credentials, OCR data, or selfie availability.

### Error Codes (When RENAPER Fails)

When the double-check fails, **`metadata.warnings`** contains one of the following **codes** (and **`metadata.responseDoubleChecks.renaper.errorCode`** is set to the same value). Your UI should translate these codes into user-facing messages.

| Code                               | Meaning                                                                                                                                                                                                                   |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RENAPER_DNI_MISSING`              | No document number (DNI) was obtained from the KYC verification.                                                                                                                                                          |
| `RENAPER_GENDER_MISSING`           | Gender (M/F) is required to call RENAPER and was not available.                                                                                                                                                           |
| `RENAPER_VERIFICATION_UNAVAILABLE` | Could not complete the cross-check with the registry (e.g. network/API error). Retry later.                                                                                                                               |
| `RENAPER_DNI_NOT_MATCH`            | The document number from the verification does not match the official registry.                                                                                                                                           |
| `RENAPER_TRAMITE_DATA_MISSING`     | Transaction number could not be compared (missing from KYC or RENAPER response).                                                                                                                                          |
| `RENAPER_TRAMITE_ID_NOT_MATCH`     | The document **transaction number** does not match the registry. Common when the person used an older copy of the ID, the document is expired, or it was reissued; they should verify again with their current, valid ID. |
| `RENAPER_NAME_NOT_MATCH`           | OCR name is below the 80% similarity threshold vs the RENAPER registry.                                                                                                                                                   |
| `RENAPER_EJEMPLAR_NOT_MATCH`       | ID copy letter (ejemplar) does not match the RENAPER registry.                                                                                                                                                            |
| `RENAPER_DOB_NOT_MATCH`            | OCR date of birth does not match the RENAPER registry.                                                                                                                                                                    |
| `RENAPER_EXPIRY_NOT_MATCH`         | OCR expiration date does not match the RENAPER registry.                                                                                                                                                                  |
| `RENAPER_BIOMETRIC_NOT_MATCH`      | Selfie does not match the ID photo in the biometric registry.                                                                                                                                                             |
| `RENAPER_BIOMETRIC_UNAVAILABLE`    | Biometric check could not be completed (network, credentials, or service).                                                                                                                                                |

### When RENAPER enforcement applies (automatic rejection)

| OCR KYC verification status                                                                    | RENAPER runs?                | Can RENAPER reject?                                          |
| ---------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------ |
| **`approved`** (direct approval)                                                               | Yes                          | **Yes** — cross-check failure → `rejected`                   |
| **`in_review`**                                                                                | Yes                          | **No** — informational; compliance decides via manual review |
| **`rejected`** (OCR verification or Gu1 rules)                                                 | Yes (if OCR available)       | **No** — informational; registry data kept in metadata       |
| **Manual approve** from `in_review` ([`POST …/approve`](/en/use-cases/kyc/approve-validation)) | **No** (reuses stored check) | **No** — human decision                                      |

### Why the validation may be rejected

RENAPER can change **status** to **`rejected`** only on the **direct `approved`** path. On `in_review` / `rejected` / manual approve, failures appear as **codes** in `metadata.warnings` and in `metadata.responseDoubleChecks.renaper` without auto-rejection. Use the table above and translation keys (e.g. `kyc.rejectionReasonCodes.*`).

## Next Steps

After creating a validation:

1. **Extract the verification URL** from `providerSessionUrl`
2. **Share the URL with your customer** via email, SMS, or in-app
3. **Set up webhook endpoint** to receive completion notifications
4. **Monitor validation status** using the validation ID
5. **After approval**, read media keys from `decision` and download files — see [Get KYC validation media](/en/use-cases/kyc/validation-media)

<CardGroup cols={2}>
  <Card title="Get KYC URL" icon="link" href="/en/use-cases/kyc/get-kyc-url">
    Learn how to retrieve the URL
  </Card>

  <Card title="Webhook Integration" icon="webhook" href="/en/use-cases/kyc/webhook-integration">
    Configure webhook notifications
  </Card>
</CardGroup>
