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

# Verificación KYC y KYB

> Verificación de identidad con wizard hosteado: formulario, documentos con OCR y prueba de vida en video — para tu cuenta y para tus clientes

export const EnvUrls = ({lang = "en"}) => {
  const T = {
    en: {
      test: "Test",
      live: "Live",
      hint: "Same API on both environments — build against test first, then go live by swapping the base URL and the key.",
      guide: "Environments and testing",
      href: "/en/environment-testing"
    },
    es: {
      test: "Test",
      live: "Live",
      hint: "La misma API en ambos ambientes — construye primero contra test y pasa a live cambiando la URL base y la key.",
      guide: "Entorno y pruebas",
      href: "/es/environment-testing"
    },
    zh: {
      test: "Test",
      live: "Live",
      hint: "两个环境的 API 完全一致——先在 test 环境构建，再通过切换基础 URL 和密钥上线。",
      guide: "环境与测试",
      href: "/zh/environment-testing"
    }
  };
  const t = T[lang] || T.en;
  const row = (label, url, keyPattern, badgeCls) => <div className="flex flex-wrap items-center gap-2 px-3 py-2">
      <span className={"rounded px-1.5 py-0.5 text-xs font-semibold uppercase tracking-wide " + badgeCls}>
        {label}
      </span>
      <code className="text-xs">{url}</code>
      <span className="text-xs text-gray-400 dark:text-zinc-500">·</span>
      <code className="text-xs">{keyPattern}</code>
    </div>;
  return <div className="my-4 rounded-xl border border-gray-200 dark:border-zinc-700 divide-y divide-gray-200 dark:divide-zinc-700 text-sm not-prose">
      {row(t.test, "https://cryptobank.qbank.cl/platform", "pk_test_...", "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300")}
      {row(t.live, "https://api.qbank.cl/platform", "pk_...", "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300")}
      <p className="px-3 py-2 text-xs text-gray-500 dark:text-zinc-400 m-0">
        {t.hint} <a href={t.href}>{t.guide} →</a>
      </p>
    </div>;
};

<EnvUrls lang="es" />

## Verificación de la cuenta antes de crear payins

La cobranza autenticada requiere `kyc_status: approved` en personas y KYB aprobado en empresas; mientras tanto, `POST /v1/payins` (QR/card/checkout/bank\_transfer/fintoc), OTP/collect, cuentas de depósito dedicadas, cobros con tarjeta guardada y suscripciones responden `403 verification_required`. Los links públicos, abonos pasivos, asignación de org-admin e instrumentos del sistema siguen disponibles; solicita `POST /v1/me/verification/link` y reintenta después de aprobar.

La **verificación de identidad** comprueba que una persona (KYC) o empresa
(KYB) es quien dice ser, con evidencia real: formulario completo, subida de
documentos validados por OCR y **prueba de vida en video**. Tiene dos caras:

1. **Tu propia verificación (onboarding)** — obligatoria: hasta aprobarla, tu cuenta solo puede **fondear** (payins, depósitos crypto, recibir
   transferencias) y consultar. Persona ⇒ KYC; empresa ⇒ KYB.
2. **Verificación de tus clientes (solo cuentas empresa)** — generas links
   hosteados o envías los datos por API para verificar a tus propios
   clientes finales, con una comisión fija por verificación.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
flowchart LR
    crear["POST /v1/kyc/links o /v1/kyb/links<br/>(fee fijo)"] --> link["Link hosteado<br/>status: pending"]
    link -->|"tu cliente lo abre"| abierto["opened"]
    abierto -->|"formulario + documentos<br/>+ prueba de vida"| completado["completed<br/>(webhook link_completed)"]
    completado --> revision["Submission<br/>pending_review → in_review"]
    revision -->|"aprobada"| ok["approved (webhook)"]
    revision -->|"faltan datos"| cambios["changes_requested /<br/>more_info_required"]
    revision -->|"rechazada"| rechazo["rejected (webhook)"]
```

## Tu propia verificación (onboarding)

Al registrarte, tu cuenta nace sin verificar (`kyc_status: none`) y **solo
puede fondear y leer**. Cualquier acción de dinero saliente (payouts,
transferencias, retiros, banking, tarjetas) responde
`403 verification_required` hasta que apruebes.

<Note>Las solicitudes nuevas exigen TOTP confirmado o passkey registrado; revisa [Perfil y seguridad](/es/guides/profile) para enrolar uno y reintenta. Los links abiertos se re-sirven antes del gate; la automatización con API key sigue exenta.</Note>

<Steps>
  <Step title="Pide tu link de verificación">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST https://api.qbank.cl/platform/v1/me/verification/link \
      -H "Authorization: Bearer <token>"
    ```

    Respuesta `201` (si ya tienes un link vigente, se devuelve el mismo con
    `200`):

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "link_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "kind": "kyc",
      "url": "https://…/on/usd/individual/new?invite=abc123…",
      "status": "pending",
      "label": "Ana Pérez",
      "created_at": "2026-07-10T12:00:00Z",
      "updated_at": "2026-07-10T12:00:00Z"
    }
    ```

    El `kind` sale de tu tipo de cuenta: persona ⇒ `kyc`, empresa ⇒ `kyb`. El
    onboarding **no tiene costo** para ti.
  </Step>

  <Step title="Completa el wizard">
    Abre la `url`: el wizard hosteado te guía por el formulario, la subida de
    documentos (identidad, comprobante de domicilio; societarios para
    empresas) y — en KYC — la prueba de vida en video frente a la cámara.
  </Step>

  <Step title="Espera la revisión">
    Consulta tu estado cuando quieras:

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl https://api.qbank.cl/platform/v1/me/verification \
      -H "Authorization: Bearer <token>"
    ```

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "kyc_status": "pending_review",
      "required_kind": "kyc",
      "verified": false,
      "link": { "link_id": "a1b2c3d4-…", "kind": "kyc", "url": "https://…", "status": "completed" },
      "submission": {
        "submission_id": "f0e1d2c3-…",
        "kind": "kyc",
        "status": "changes_requested",
        "liveness_pending": false,
        "pending_documents": ["proofOfResidence"],
        "changes_requested_comments": "Sube un comprobante de residencia legible.",
        "documents_received": ["identity"],
        "documents_gate": {
          "ok": false,
          "matched": 2,
          "total": 10,
          "unresolved": [
            { "category": "bankStatement", "effective_outcome": "REVIEW" }
          ]
        },
        "created_at": "2026-07-10T12:05:00Z",
        "updated_at": "2026-07-11T16:00:00Z"
      }
    }
    ```

    Los cuatro campos opcionales de `submission` aparecen cuando están disponibles:
    `pending_documents`, `changes_requested_comments`, `documents_received` y
    `documents_gate`; `rejection_reason` no se expone y una falla temporal del
    core puede omitirlos.

    Cuando compliance aprueba, tu `kyc_status` pasa a `approved`
    **automáticamente**, todos los servicios se desbloquean y recibes el
    webhook `kyc_verification_status_changed` con `self_onboarding: true`.

    <Note>
      **Motor de decisión automática:** un expediente completamente limpio (documentos legibles, liveness aprobado, sin sanciones ni coincidencias PEP y sin señales de riesgo) se aprueba **en segundos y sin intervención humana**. Las zonas grises pasan a la cola humana del operador. Las sanciones AML severas (incluida una parte KYB), los países prohibidos, el fraude/expiración documental explícita y los rechazos explícitos del proveedor también pasan a la cola humana con sus razones preservadas; el motor ya no auto-rechaza. El campo `decision_source` del webhook (`"auto"` / `"admin"`) indica quién decidió.
    </Note>

    La aprobación además **rellena el perfil de tu cuenta con la identidad
    verificada**: `display_name` (persona = nombre + apellido; empresa = razón
    social), `tax_id` y `country` se toman de la verificación y desde ese
    momento quedan **inmutables** vía `PATCH /v1/me` (`409 identity_locked`) —
    la identidad verificada es la fuente de verdad.
  </Step>
</Steps>

<Note>
  Mientras esperas puedes fondear normalmente: payins, depósitos crypto y
  transferencias entrantes funcionan desde el día uno. Si la verificación es
  rechazada (`kyc_status: rejected`), contacta a tu operador por un link nuevo.
</Note>

## Verificar a tus clientes (solo cuentas empresa)

Una cuenta **empresa** verificada puede verificar a sus propios clientes
finales. Cada verificación creada cobra la comisión fija configurada
(`kyc_verification` / `kyb_verification`; 0 = gratis), que se **reembolsa
automáticamente** si la creación falla. Las cuentas persona reciben
`403 company_account_required`.

### Opción A — Links hosteados (recomendada)

Tu cliente completa TODO en el wizard de marca blanca: formulario,
documentos y prueba de vida. Tú solo generas el link y esperas el webhook.

<CodeGroup>
  ```bash Link KYC (persona) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://api.qbank.cl/platform/v1/kyc/links \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "external_customer_id": "cust_123",
      "label": "Ana Pérez",
      "expires_in_days": 14,
      "idempotency_key": "kyc-link-cust-123-1"
    }'
  ```

  ```bash Link KYB (empresa, con país) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://api.qbank.cl/platform/v1/kyb/links \
    -H "Authorization: Bearer <token>" \
    -H "Content-Type: application/json" \
    -d '{
      "external_customer_id": "cust_456",
      "country": "cl",
      "label": "Comercial Andina SpA",
      "expires_in_days": 14,
      "idempotency_key": "kyb-link-cust-456-1"
    }'
  ```
</CodeGroup>

* `external_customer_id` (obligatorio): TU referencia del cliente
  verificado — la recibes de vuelta en cada webhook y consulta. Los valores
  `self` o terminados en `:self` están reservados para el onboarding de la
  cuenta y se rechazan con `400 invalid_payload`.
* `idempotency_key` (obligatoria): un retry con la misma clave devuelve el
  link original y **nunca cobra dos veces**.
* `country` (solo KYB): `us`, `cl`, `ve`, `br`, `mx`, `co`, `pe`, `bo`,
  `py`, `ar` o `generic` (con `generic_country` ISO alpha-2, ej. `"ES"`).
  El KYC individual no lleva país.
* `expires_in_days` (opcional, 1–30): sin enviarlo, el link no vence.

Respuesta `201`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "link_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
  "kind": "kyb",
  "external_customer_id": "cust_456",
  "url": "https://…/on/cl/business/new?invite=abc123…",
  "status": "pending",
  "country": "cl",
  "label": "Comercial Andina SpA",
  "expires_at": 1721209600,
  "verification_fee": "2.000000",
  "created_at": "2026-07-10T12:00:00Z",
  "updated_at": "2026-07-10T12:00:00Z"
}
```

Consulta e historial (todo POST tiene su GET):

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# Listado con filtros
curl "https://api.qbank.cl/platform/v1/kyb/links?from=2026-07-01&to=2026-07-10&status=completed&page=1&page_size=50" \
  -H "Authorization: Bearer <token>"

# Detalle (estado vivo del link)
curl https://api.qbank.cl/platform/v1/kyb/links/{link_id} \
  -H "Authorization: Bearer <token>"
```

| Estado del link | Significado |
| - | - |
| `pending` | Creado, tu cliente aún no lo abre |
| `opened` | Tu cliente abrió el wizard |
| `completed` | Formulario enviado — nace la submission (webhook `kyb_link_completed` / `kyc_link_completed`) |
| `expired` | Venció sin completarse |

### Opción B — Datos por API

Si ya tienes los datos del cliente en tu sistema, créale la verificación
directo (sin wizard). La submission entra al mismo queue de revisión:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.qbank.cl/platform/v1/kyc/submissions \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "external_customer_id": "cust_789",
    "idempotency_key": "kyc-sub-cust-789-1",
    "person": {
      "first_name": "Ana",
      "last_name": "Pérez",
      "email": "ana@ejemplo.com",
      "phone": "+56912345678",
      "nationality": "CHL",
      "date_of_birth": "1990-04-12",
      "tax_id": "12.345.678-5",
      "id_type": "id_card",
      "id_number": "12345678",
      "address": {
        "line1": "Av. Siempre Viva 123",
        "city": "Santiago",
        "state": "RM",
        "postal_code": "8320000",
        "country": "CHL"
      },
      "primary_purpose": "personal_or_living_expenses",
      "most_recent_occupation": "Engineer",
      "source_of_funds": "salary"
    }
  }'
```

Notas del modo datos:

* Países en **ISO alpha-3** (`CHL`, `USA`, `VEN`…); fechas `YYYY-MM-DD`;
  `id_type`: `passport | id_card | drivers_license`.
* KYB: body `{ external_customer_id, country?, business: {…}, ubos?,
  directors?, signers?, bank_info?, metadata? }` en
  `POST /v1/kyb/submissions`.
* **No se exige prueba de vida al crear**: la submission KYC queda con
  `liveness_pending: true` y la cierras con un [liveness
  link](#prueba-de-vida-liveness-link).
* Re-enviar con el mismo `external_customer_id` mientras la submission
  siga abierta (`pending_review`, `changes_requested`,
  `more_info_required`) **actualiza** la misma submission y no cobra de
  nuevo.

Respuesta `201`:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "submission_id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f",
  "kind": "kyc",
  "external_customer_id": "cust_789",
  "status": "pending_review",
  "liveness_pending": true,
  "verification_fee": "1.500000",
  "created_at": "2026-07-10T12:05:00Z",
  "updated_at": "2026-07-10T12:05:00Z"
}
```

Consulta e historial:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.qbank.cl/platform/v1/kyc/submissions?from=2026-07-01&to=2026-07-10&status=approved&page=1&page_size=50" \
  -H "Authorization: Bearer <token>"

curl https://api.qbank.cl/platform/v1/kyc/submissions/{submission_id} \
  -H "Authorization: Bearer <token>"
```

El detalle agrega `pending_documents`, `rejection_reason`,
`changes_requested_comments`, `documents_received`, `documents_gate` y
`aml_decision` para KYB.

| Estado de la submission | Significado |
| - | - |
| `pending_review` | Recibida, en cola de compliance |
| `in_review` | Compliance tomó el caso |
| `changes_requested` | Hay que corregir datos y re-enviar |
| `more_info_required` | Faltan documentos ([súbelos por API](#documentos-por-api)) |
| `escalated` | Caso escalado a revisión senior |
| `approved` / `approved_partial` | Aprobada (final) |
| `rejected` | Rechazada (final) |

### Documentos por API

Los documentos son opcionales al crear (si faltan, compliance los pedirá
con `more_info_required`). Flujo de 3 pasos:

<Steps>
  <Step title="Presign">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST https://api.qbank.cl/platform/v1/kyc/submissions/{submission_id}/documents \
      -H "Authorization: Bearer <token>" \
      -H "Content-Type: application/json" \
      -d '{
        "category": "identity",
        "filename": "cedula.jpg",
        "content_type": "image/jpeg",
        "file_size": 482133
      }'
    ```

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    { "upload_url": "https://storage…", "key": "public-api/…", "expires_in": 900 }
    ```

    Categorías — KYC: `identity`, `proofOfResidence`; KYB: `legalPresence`,
    `ownershipStructure`, `controlStructure`, `companyDetails`. Tipos:
    `application/pdf`, `image/png`, `image/jpeg`; máximo 15 MB; la URL vence en
    15 minutos.
  </Step>

  <Step title="Upload">
    `PUT` del binario directo a `upload_url` con el mismo `Content-Type`.
  </Step>

  <Step title="Confirm">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST https://api.qbank.cl/platform/v1/kyc/submissions/{submission_id}/documents/confirm \
      -H "Authorization: Bearer <token>" \
      -H "Content-Type: application/json" \
      -d '{ "key": "public-api/…", "category": "identity", "filename": "cedula.jpg", "content_type": "image/jpeg" }'
    ```

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    { "status": "received", "ocr": "queued" }
    ```

    Al confirmar se dispara la validación OCR; el resultado llega por el
    webhook `kyc_document_validated` / `kyb_document_validated` y se consulta
    con GET:

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl https://api.qbank.cl/platform/v1/kyc/submissions/{submission_id}/documents \
      -H "Authorization: Bearer <token>"
    ```

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "items": [
        {
          "id": "9c9b0f1e-4b3c-4f6a-9f6d-2f0a1b2c3d4e",
          "category": "identity",
          "status": "completed",
          "outcome": "MATCH",
          "effective_outcome": "MATCH",
          "score": 0.97,
          "summary": "Document matches the submitted identity",
          "filename": "cedula.jpg"
        }
      ],
      "meta": { "retrieved": 1 }
    }
    ```

    `outcome`: `MATCH` (coincide), `REVIEW` (revisión manual), `NO_MATCH`. Cada item expone además:

    * `id`: identificador de la validación (lo usa el equipo de cumplimiento para revisarla).
    * `effective_outcome`: el resultado que rige hoy — si un administrador resolvió la validación manualmente desde el panel admin, ese resultado; si no, el del motor OCR. En el detalle de la submission (`GET /v1/kyc/submissions/{id}`) el bloque `documents_gate` resume si TODOS los documentos ya están resueltos (`ok: true`), con `matched`/`total` y la lista `unresolved` de los pendientes.
    * `manual_review`: presente solo cuando un administrador resolvió manualmente la validación desde el panel admin. En la vista de cuenta trae `outcome` y `reviewed_at` (sin la nota interna ni el revisor).

    <Note>
      La revisión manual de una validación de documento es una operación exclusiva del panel de administración (CBPay Admin); no se expone por API pública. Cuando el equipo de cumplimiento la aplica, tu cuenta ve el resultado actualizado en `effective_outcome` y `manual_review`, y el webhook `kyc_document_validated` / `kyb_document_validated` correspondiente.
    </Note>
  </Step>
</Steps>

### Prueba de vida (liveness link)

Las submissions KYC creadas por datos nacen con `liveness_pending: true`
(la prueba de vida es un flujo de cámara en browser). Genera un link
mínimo hosteado para que tu cliente la complete:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.qbank.cl/platform/v1/kyc/submissions/{submission_id}/liveness_link \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{ "expires_in_days": 7 }'
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "url": "https://…/on/liveness/<token>", "status": "pending", "expires_at": 1751234567 }
```

* No cobra (el servicio se cobró al crear la submission). Si ya hay un
  link vigente, el POST devuelve el mismo; si la prueba ya fue superada,
  `400 liveness_already_completed`.
* `GET .../liveness_link` devuelve el último link y el estado del check
  (`{ "liveness": { "status", "outcome", "passed" } }`).
* Al pasar (outcome `PASS` o `REVIEW`): la submission limpia
  `liveness_pending` y llega el webhook `kyc_liveness_completed`.

## Una sola verificación para todo (identidad reutilizable)

La verificación aprobada de un cliente es su **identidad única** dentro de
CBPay: no vuelves a tipear sus datos ni a subir sus documentos en ningún
otro producto.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
flowchart LR
    verif["Verificación aprobada<br/>(submission_id)"] -->|"verification_id"| banking["Alta banking de terceros<br/>POST /v1/banking/third-parties"]
    verif -->|"cardholder.verification_id"| card["Tarjeta para persona designada<br/>POST /v1/cards"]
    verif -.->|"mismo patrón"| futuro["Próximos productos"]
```

* **Banking para terceros**: `POST /v1/banking/third-parties` exige el
  `verification_id` de una verificación **aprobada** del tercero. El tipo
  (`INDIVIDUAL`/`COMPANY`) sale del kind (KYC ⇒ persona, KYB ⇒ empresa),
  los datos (nombre, email, dirección) se completan solos desde el perfil
  verificado, y los documentos ya validados se re-entregan automáticamente
  al proveedor bancario (`documents_synced` en la respuesta). Detalle en
  [Banking](/es/guides/banking#usuarios-banking-de-terceros-solo-empresas).
* **Tarjetas para personas designadas**: `POST /v1/cards` con un
  `cardholder` de persona exige `cardholder.verification_id` de un **KYC
  aprobado** de esa persona. Identidad y documentos del titular salen de la
  verificación; solo agregas los campos propios del emisor (`occupation`,
  `salary_usd`). Detalle en [Tarjetas](/es/guides/cards).
* **Tu propia cuenta**: tu onboarding aprobado también se reutiliza — al
  crear tu banking customer o tu primera tarjeta, los datos y documentos
  faltantes se completan desde tu verificación.

Los campos explícitos de tu request **siempre ganan** sobre el autofill.

<Warning>
  Sin una verificación aprobada del tercero, el alta banking y la emisión de
  tarjetas designadas responden `422 verification_required`. Verifica primero
  (links hosteados o datos por API) y usa el `submission_id` aprobado como
  `verification_id`.
</Warning>

## Informe de compliance (solo KYB)

Para cada verificación KYB puedes descargar el **informe de compliance
firmado** (PDF, evidencia para tus propios auditores):

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -o report.pdf https://api.qbank.cl/platform/v1/kyb/submissions/{submission_id}/report \
  -H "Authorization: Bearer <token>"
```

Es gratuito (el servicio se cobró al crear la verificación).

## Informe de verificación (PDF + JSON)

Además del informe del procesador, cada submission KYC o KYB decidida tiene
su **informe de verificación** generado por la plataforma. Es el expediente
completo, no un resumen: identidad verificada (persona o empresa), perfil
económico declarado, declaraciones de riesgo, cuenta bancaria enmascarada,
ciclo de vida de la decisión, documentos con su validación documental, prueba
de vida, **partes relacionadas con su propio screening** (KYB) y screening
AML con el detalle de cada coincidencia — todo con hash de integridad y
código de verificación pública. Dos formatos (`?format=pdf|json`, default
`pdf`) y tres idiomas (`?lang=en|es|zh`, default `en`). Es gratuito: es la
lectura de una verificación ya pagada.

Secciones del informe:

| Sección | Contenido |
| - | - |
| Sujeto | Identidad verificada: persona (documento, nacionalidad, residencia fiscal, ocupación) o empresa (registro, constitución, jurisdicción, industria ISIC, sitio) |
| Perfil económico | Origen de fondos, propósito de la relación, volúmenes e ingresos esperados, redes esperadas |
| Declaraciones | Respuestas de riesgo declaradas (servicios monetarios, fondos de terceros, actividades de alto riesgo, países prohibidos) |
| Cuenta bancaria | Banco, titular y número **enmascarado en origen** (nunca completo) |
| Documentos | Categoría, archivo, estado, resultado de validación, score, fecha de validación y motivo de rechazo. El PDF agrega **fotos del documento de identidad** cuando el proveedor las entrega (si no hay, la sección se omite) |
| Prueba de vida | Por rol (titular, UBO N): resultado, gate, scores de liveness / antispoofing / similitud facial. **Una fila por sesión** — un sujeto puede tener el check de onboarding `gate` más una o más recapturas de evidencia `media_recapture` posteriores, cada una con su propio `session_id` y `purpose`. El PDF embebe selfie/frames cuando hay media viva; el JSON solo declara metadata (`has_selfie`, `has_video`, `frame_gestures`, hashes) sin URLs |
| Partes relacionadas | Solo KYB: UBOs, personas de control y firmantes, cada una con identidad, participación, sus documentos, **todas sus sesiones de liveness** (`liveness_sessions[]`) y **su propio screening AML** |
| Screening AML | Nivel de riesgo, indicadores, coincidencias con alias, listas de sanciones con fuente y vigencia, posiciones PEP, vínculos RCA y medios adversos. El cierre del PDF incluye el anexo AML completo (atribución y fuentes) cuando hubo screening |

<Note>
  **La prueba de vida trae una fila por sesión, no una por sujeto.** La sesión
  `gate` es el check que habilitó el onboarding — normalmente trae solo la
  selfie. La sesión `media_recapture` es una captura de evidencia posterior y
  es la que trae el paquete completo (selfie + un frame por gesto solicitado +
  video). Una `media_recapture` que terminó en `outcome: "FAIL"` sigue
  importando — puede ser la única sesión con video utilizable — así que
  **recorre todo el array `liveness[]`** en vez de leer `liveness[0]`; el
  veredicto vigente del sujeto es siempre el `outcome` de la sesión `gate`. En
  un KYB, `parties[].liveness` (singular) se conserva por compatibilidad y
  siempre apunta a la sesión `gate` de esa parte, mientras
  `parties[].liveness_sessions[]` trae todas las sesiones de esa parte.
</Note>

<Note>
  **Cómo se lee el PDF.** El informe abre con una **portada navegable**: un
  índice de tarjetas con icono, título y número de página que son **clicables**
  y saltan a su sección. Cada sección lleva su propio icono y barra de acento
  (el mismo lenguaje visual del informe AML), las fotos del documento y de la
  prueba de vida conservan su proporción real, y ningún encabezado queda solo
  al pie de una página. Los medios adversos del anexo AML llevan un chip
  **“ver fuente”** y la URL de verificación pública del cierre es clicable —
  por seguridad **solo se embeben enlaces `http` y `https`**; cualquier otro
  esquema se descarta y el texto queda sin enlace.
</Note>

### De tus terceros (cuentas empresa)

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# PDF en español
curl -o informe.pdf "https://api.qbank.cl/platform/v1/kyb/submissions/{submission_id}/verification-report?lang=es" \
  -H "Authorization: Bearer <token>"

# JSON (mismo contenido que el PDF)
curl "https://api.qbank.cl/platform/v1/kyc/submissions/{submission_id}/verification-report?format=json" \
  -H "Authorization: Bearer <token>"
```

El informe de un tercero es **completo**: la sección AML incluye nivel de
riesgo, indicadores y coincidencias (nombres, listas de sanciones, PEP,
medios adversos). Tú realizas la diligencia sobre tu cliente y este informe
es tu evidencia.

Respuesta `format=json` (resumen del shape — el PDF sale del mismo modelo):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "report_id": "IDR-C3D4E5F6A7B8",
  "kind": "kyb",
  "scope": "third_party",
  "generated_at": "2026-07-27T19:04:11Z",
  "generated_by": "CBPay",
  "language": "es",
  "aml_detail": true,
  "submission_id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f",
  "external_customer_id": "cust_789",
  "status": "approved",
  "risk_band": "low",
  "aml_decision": "no_match",
  "subject": {
    "type": "company",
    "name": "Importadora Andina SpA",
    "company": {
      "legal_name": "Importadora Andina SpA",
      "registration_number": "77.123.456-7",
      "incorporation_date": "2019-04-12",
      "incorporation_country": "CHL",
      "website": "https://andina.example",
      "countries_of_operation": ["CL", "PE"]
    },
    "address": { "city": "Santiago", "country": "CL" },
    "registered_address": { "line1": "Av. Apoquindo 1234", "city": "Santiago", "country": "CL" },
    "industry": { "code": "G4690", "label": "Comercio al por mayor" },
    "economic_profile": {
      "source_of_funds": "business_revenue",
      "primary_purpose": "supplier_payments",
      "annual_revenue_usd": "250000",
      "monthly_payments_usd": "40000",
      "expected_chains": ["tron", "ethereum"]
    },
    "attestations": [
      { "key": "att_money_services", "value": false },
      { "key": "att_high_risk_activities", "items": [] }
    ],
    "bank_account": {
      "bank_name": "Banco de Chile",
      "account_holder": "Importadora Andina SpA",
      "account_masked": "****4321",
      "country": "CL",
      "currency": "CLP"
    }
  },
  "parties": [
    {
      "source": "ubo",
      "index": 0,
      "kind": "ubo",
      "name": "Javier Villablanca",
      "person": {
        "first_name": "Javier",
        "last_name": "Villablanca",
        "date_of_birth": "1985-03-04",
        "nationality": "CL",
        "id_type": "national_id",
        "id_number": "12345678-9"
      },
      "address": { "city": "Santiago", "country": "CL" },
      "ownership_percent": "60",
      "has_ownership": true,
      "has_control": true,
      "documents": [
        { "category": "uboIdentity:0", "status": "validated", "outcome": "MATCH", "party_index": 0 }
      ],
      "liveness": {
        "role": "ubo:0",
        "session_id": "lv_e763e3465bf34f1dab826a263c1eaaaa",
        "purpose": "gate",
        "status": "completed",
        "outcome": "PASS",
        "passed_gate": true,
        "liveness_score": "0.86",
        "media": { "has_selfie": true, "has_video": false, "expires_in_sec": 900 }
      },
      "liveness_sessions": [
        {
          "role": "ubo:0",
          "session_id": "lv_e763e3465bf34f1dab826a263c1eaaaa",
          "purpose": "gate",
          "status": "completed",
          "outcome": "PASS",
          "passed_gate": true,
          "liveness_score": "0.86",
          "media": { "has_selfie": true, "has_video": false, "expires_in_sec": 900 }
        },
        {
          "role": "ubo:0",
          "session_id": "lv_4cdd3a82903940cebd8cc95a77cdacb3",
          "purpose": "media_recapture",
          "status": "completed",
          "outcome": "FAIL",
          "passed_gate": false,
          "liveness_score": "0.98",
          "reasons": ["No se detectó la sonrisa."],
          "media": {
            "has_selfie": true,
            "has_video": true,
            "frame_gestures": ["center", "turn_right", "smile"],
            "video_mime_type": "video/mp4",
            "expires_in_sec": 900
          }
        }
      ],
      "aml": {
        "screening_id": "d5f6a7b8-9c0d-4e1f-2a3b-4c5d6e7f8a9b",
        "risk_level": "no_risk",
        "sanctions": "clear",
        "pep": "clear",
        "adverse_media": "clear",
        "monitor": true,
        "matches_total": 0
      }
    }
  ],
  "documents": [
    {
      "category": "registration",
      "filename": "escritura.pdf",
      "status": "validated",
      "outcome": "MATCH",
      "score": "0.97",
      "validated_at": "2026-07-26T14:02:00Z"
    }
  ],
  "liveness": [
    {
      "role": "ubo:0",
      "session_id": "lv_e763e3465bf34f1dab826a263c1eaaaa",
      "purpose": "gate",
      "status": "completed",
      "outcome": "PASS",
      "passed_gate": true,
      "liveness_score": "0.86",
      "media": { "has_selfie": true, "has_video": false, "expires_in_sec": 900 }
    },
    {
      "role": "ubo:0",
      "session_id": "lv_4cdd3a82903940cebd8cc95a77cdacb3",
      "purpose": "media_recapture",
      "status": "completed",
      "outcome": "FAIL",
      "passed_gate": false,
      "liveness_score": "0.98",
      "reasons": ["No se detectó la sonrisa."],
      "media": {
        "has_selfie": true,
        "has_video": true,
        "frame_gestures": ["center", "turn_right", "smile"],
        "video_mime_type": "video/mp4",
        "expires_in_sec": 900
      }
    }
  ],
  "aml": {
    "screening_id": "b7e1c2d3-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
    "risk_level": "no_risk",
    "status": "no_hits",
    "monitor": true,
    "screened_at": "2026-07-26T14:05:12Z",
    "sanctions": "clear",
    "pep": "clear",
    "adverse_media": "clear",
    "screening_result": "no_hits",
    "indicators": [
      { "key": "ind_sanctions", "hit": false },
      { "key": "ind_pep", "hit": false },
      { "key": "ind_adverse_media", "hit": false }
    ],
    "subject_rows": [
      { "key": "legal_name", "value": "Importadora Andina SpA" }
    ],
    "matches_total": 0
  },
  "content_sha256": "9f2b4c…",
  "verification_code": "Bc3d4e5f6a7b84c9d0e1f2a3b4c5d6e7f9f2b4c6d8e0a1b3c5d7",
  "verification_url": "https://api.qbank.cl/platform/verify/reports/Bc3d4e5f6a7b84c9d0e1f2a3b4c5d6e7f9f2b4c6d8e0a1b3c5d7"
}
```

<Note>
  Si la verificación aún no tiene screening AML enlazado (verificaciones
  antiguas), la primera descarga lo ejecuta automáticamente **sin costo**. Si
  el screening no está disponible en ese momento, el informe sale igual con
  `"partial": ["aml_unavailable"]` — la sección jamás se inventa.
</Note>

### Partes relacionadas y su screening (KYB)

En una verificación de empresa, cada UBO, persona de control y firmante del
expediente sale como una entrada de `parties[]` con su identidad completa,
su participación, los documentos y la prueba de vida que le corresponden, y
**su propio screening AML con monitoreo continuo activado**. El par
`(source, index)` es la identidad estable de la parte dentro del expediente:
es lo que enlaza sus documentos (`uboIdentity:0`) y lo que hace que su
screening sea siempre el mismo, sin importar cuántas veces descargues el
informe.

El screening de las partes es **gratuito** (es una obligación de diligencia,
no un producto facturable) y queda bajo monitoreo: si un UBO entra a una
lista de sanciones después del alta, la alerta aparece sola.

<Note>
  Si alguna parte todavía no tiene su screening al momento de la descarga, el
  informe sale con `"partial": ["party_aml_unavailable"]` y el screening
  faltante se ejecuta en segundo plano: la descarga siguiente ya lo trae.
</Note>

### De tu propio onboarding

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -o informe.pdf "https://api.qbank.cl/platform/v1/me/verification/report?lang=es" \
  -H "Authorization: Bearer <token>"
```

En el informe de tu propia verificación la sección AML va **agregada**
(`aml_detail: false`): verás el estado por categoría — `sanctions`, `pep` y
`adverse_media` como `clear` o `under_review` — sin el detalle de
coincidencias. Lo mismo aplica al screening de tus partes relacionadas: sus
secciones AML también van agregadas. El resto del expediente (identidad,
perfil económico, documentos, prueba de vida, partes) sale completo.

### Verificación pública del informe

Todo informe lleva un `verification_code` (impreso en el PDF junto a un QR).
Cualquiera puede confirmar su autenticidad sin credenciales:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "https://api.qbank.cl/platform/verify/reports/Bc3d4e5f6a7b84c9d0e1f2a3b4c5d6e7f9f2b4c6d8e0a1b3c5d7"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "valid": true,
  "type": "verification_report",
  "kind": "kyb",
  "status": "approved",
  "decision": "approved",
  "date": "2026-07-27",
  "issued_by": "CBPay"
}
```

La página pública confirma solo el tipo, el estado vigente de la decisión, la
fecha y la marca emisora — nunca datos del sujeto. En un navegador responde
una página HTML con tu marca.

## Webhooks

| Evento | Cuándo |
| - | - |
| `kyc_verification_status_changed` / `kyb_verification_status_changed` | La submission cambió de estado (todo el ciclo: recibida, en revisión, cambios pedidos, aprobada, rechazada…) |
| `kyc_link_completed` / `kyb_link_completed` | Tu cliente completó un link hosteado |
| `kyc_document_validated` / `kyb_document_validated` | Terminó el OCR de un documento subido por API |
| `kyc_liveness_completed` | La prueba de vida fue completada desde un liveness link |

Payload de ejemplo (`kyc_verification_status_changed`):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "account_id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "kind": "kyc",
  "event": "approved",
  "submission_id": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f",
  "external_customer_id": "cust_789",
  "status": "approved",
  "risk_band": "low",
  "decision": "approved"
}
```

Tu propio onboarding llega con `"self_onboarding": true` en vez de
`external_customer_id`. Suscríbete igual que al resto de eventos (ver
[Webhooks](/es/webhooks)).

## Costos (configurados por tu operador, pueden ser 0)

| Servicio | Cuándo se cobra |
| - | - |
| `kyc_verification` | Al crear un link o submission KYC de un tercero |
| `kyb_verification` | Al crear un link o submission KYB de un tercero |

El cargo sale de tu saldo de settlement predeterminado, se reembolsa si la
creación falla, y **tu propio onboarding nunca cobra**. Re-envíos de una
submission abierta y liveness links no cobran de nuevo.

## Errores

| HTTP | `error` | Causa | Solución |
| - | - | - | - |
| 400 | `idempotency_key_required` | POST de creación sin clave | Envía `idempotency_key` (body o header) |
| 400 | `invalid_payload` | Falta `external_customer_id` u otro campo requerido | Revisa el body |
| 400 | `liveness_already_completed` | La prueba de vida ya fue superada | Nada que hacer |
| 400 | `invalid_format` | `format` inválido al pedir un informe de verificación | Usa `pdf` o `json` |
| 400 | `invalid_language` | `lang` inválido al pedir un informe de verificación | Usa `en`, `es` o `zh` |
| 402 | `insufficient_funds` | Saldo insuficiente para la comisión | Fondea la cuenta y reintenta |
| 403 | `verification_requires_2fa` | Una solicitud nueva exige un TOTP confirmado o un passkey registrado | Enrola un factor y reintenta `POST /v1/me/verification/link` |
| 403 | `verification_rejected_requires_admin` | Un administrador rechazó el KYC/KYB de la cuenta por compliance | Contacta al administrador para reabrir la verificación; el reenvío self-service no está disponible |
| 403 | `verification_required` | Tu cuenta aún no aprobó su propia verificación | Completa tu [onboarding](#tu-propia-verificacion-onboarding) |
| 403 | `company_account_required` | Una cuenta persona intentó verificar terceros | Solo cuentas empresa |
| 403 | `service_disabled` | El servicio `kyc` está deshabilitado para tu cuenta | Contacta a tu operador |
| 404 | `not_found` | El link/submission no existe o no es tuyo | Verifica el id |
| 404 | `verification_not_found` | Pediste tu informe self sin una verificación registrada | Completa tu onboarding primero |
| 409 | `already_verified` | Pediste link de onboarding con la cuenta ya aprobada | Nada que hacer |
| 503 | `verifications_unavailable` | Verificación de identidad temporalmente no disponible | Reintenta más tarde |

## Preguntas frecuentes

<AccordionGroup>
  <Accordion title="¿Por qué no puedo hacer payouts recién registrado?">
    Toda cuenta debe aprobar su verificación antes de mover dinero hacia afuera.
    Mientras tanto puedes fondear y explorar la API. Pide el link con
    `POST /v1/me/verification/link` y complétalo — la aprobación desbloquea todo.
  </Accordion>

  <Accordion title="¿Por qué la verificación me pide contactar al administrador?">Un administrador pudo haber desactivado o cerrado la cuenta, o rechazado su KYC/KYB. En esos estados, `POST /v1/me/verification/link` no vuelve a servir ni crea un link self-service. Primero el administrador debe restaurar la cuenta o reabrir la verificación.</Accordion>

  <Accordion title="¿Qué diferencia hay entre links hosteados y datos por API?">
    Con links, tu cliente completa todo en el wizard (formulario + documentos +
    prueba de vida) y tú no manejas datos sensibles. Con datos por API tú envías
    los campos y subes documentos vía presign — útil si ya tienes tu propio
    formulario — pero la prueba de vida igual requiere un liveness link (es un
    flujo de cámara, no se puede hacer server-to-server).
  </Accordion>

  <Accordion title="¿Cuándo se cobra la comisión y cuándo no?">
    Se cobra al CREAR un link o una submission de terceros (modo live). Tu
    onboarding, re-envíos abiertos, liveness links, consultas y documentos son
    gratis; si falla la creación, la comisión se reembolsa sola.
  </Accordion>

  <Accordion title="¿Por qué mi cuenta persona no puede crear links?">
    La verificación de terceros es una herramienta B2B para cuentas empresa; una
    cuenta persona solo necesita su onboarding gratis en `/v1/me/verification`.
  </Accordion>

  <Accordion title="Compliance pidió más documentos, ¿cómo los mando?">
    Recibirás `more_info_required` con `pending_documents`. Usa el flujo presign
    → upload → confirm de esta página; luego la submission vuelve a revisión.
  </Accordion>

  <Accordion title="¿Esto reemplaza al AML screening?">
    No: son complementarios. La verificación prueba la identidad con evidencia;
    el [AML screening](/es/guides/aml) consulta listas de sanciones/PEP/prensa
    adversa y puede vigilar la identidad de forma continua.
  </Accordion>

  <Accordion title="¿Puedo reusar la verificación de un cliente en otros productos?">
    Sí — una verificación aprobada es la identidad reutilizable. Pasa su
    `submission_id` como `verification_id` al dar de alta un usuario banking o
    emitir una tarjeta para una persona designada; los datos y documentos se
    completan solos. Ver [identidad reutilizable](#una-sola-verificacion-para-todo-identidad-reutilizable).
  </Accordion>

  <Accordion title="¿Recibo un email cuando aprueban o rechazan mi verificación?">
    Si es tu propia cuenta (no una empresa verificando a un tercero), recibirás
    un email brandeado cuando la decisión sea aprobada, rechazada o pida cambios.
    Nunca incluirá el motivo detallado del rechazo. Los terceros no reciben este
    email; reciben el webhook `kyc_status_changed`/`kyb_status_changed` que ya
    integraste.
  </Accordion>
</AccordionGroup>


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