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

# Contrato Marco Grupo CB

> Emite, revisa y firma el Contrato Marco Grupo CB prellenado para una cuenta empresa verificada

El Contrato Marco Grupo CB es un documento prellenado que CBPay emite para
una cuenta empresa verificada. CBPay resuelve la identidad legal, dirección
verificada, servicios activos, comisiones y firma vigente de CBPay antes de
crear el sobre. Si falta un dato obligatorio verificable, el sobre no se crea.

La ceremonia la completa una persona humana con rol owner u operator de la
cuenta empresa. Exige consentimiento explícito y un OTP fresco de un solo uso.
Las API keys no pueden firmar.

Para solicitudes de prueba usa `https://cryptobank.qbank.cl/platform`;
`https://api.qbank.cl/platform` corresponde solo a producción.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
  participant A as Admin de la organización
  participant C as Cuenta empresa
  participant P as CBPay
  participant W as Suscriptor webhook
  A->>P: POST /v1/org/contracts/envelopes
  P-->>A: sobre pending_client
  C->>P: GET sobre y PDF
  C->>P: POST /sign + X-OTP-Token
  P-->>C: sobre completed y PDF final
  P-->>W: contract_envelope_completed
```

## Elegibilidad y estados

La cuenta debe ser empresa activa con KYB aprobado. El sobre está acotado a la
organización y cuenta; un ID ajeno responde `404 not_found`.

| Estado | Significado | Próxima acción |
| - | - | - |
| `pending_client` | CBPay congeló el documento prefirmado y el cliente aún no completa la ceremonia. | Revisar y firmar, o pedir al admin emitir void/reissue. |
| `completed` | Firma, consentimiento y claim OTP exitosos; el PDF final quedó generado. | Leer o descargar el documento final. No se puede voidar. |
| `voided` | Un admin voidó el sobre pendiente con motivo. | Emitir un sobre nuevo si hay que corregirlo. |

No existen los estados `draft` ni `pending`.

Para `lang=en`, la organización debe tener
`contract_counsel_approved_en=true`. La emisión en español no requiere ese
gate de counsel en inglés. La plantilla es `v13.3`; el hash y la versión de
firma de CBPay quedan congelados en el snapshot del sobre.

## Cobertura de precios y cifras mostradas

El endpoint de emisión de la organización comprueba la cobertura de precios
antes de crear el sobre. Si un servicio habilitado no tiene una fila de fee
efectiva, responde HTTP 422 `contract_unfillable` con un array `missing` que
usa valores de la forma `pricing:<flag>`. Una fila explícita de cero por ciento
y cero fijo es una configuración gratuita válida. `transfers` y `swaps` están
exentos de este gate de filas de fee.

Operativamente, el cuerpo v13.3 ya no incluye una tabla plana de precios fijos.
El Anexo A apunta al Programa de Comisiones vigente del Cliente y agrupa las
entradas aplicables con labels localizados como Pagos fiat, Compliance,
Tarjetas, Banking, Wallets y Otros, igual que el tablero de precios. Rige la
configuración efectiva capturada al emitir; el admin debe corregir una
cobertura faltante y emitir un sobre nuevo.

Los márgenes internos `fx_spread`, `settlement_spread` y `swap_spread` no
aparecen en el `fill_snapshot` visible para la cuenta ni en el apéndice de
precios del PDF. La vista org-admin conserva el snapshot completo para
auditoría.

## Ceremonia

<Steps>
  <Step title="Lista y abre el sobre">
    Usa `GET /v1/me/contracts/envelopes` y luego
    `GET /v1/me/contracts/envelopes/{envelopeID}`. La lista usa `page` y
    `page_size` (default 50, máximo 200). La respuesta de lista usa `items`;
    el detalle incluye `events` append-only y el nombre de cada evento en
    `event`.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl "https://api.qbank.cl/platform/v1/me/contracts/envelopes?page=1&page_size=50" \
      -H "Authorization: Bearer <CBPAY_TOKEN>"
    ```
  </Step>

  <Step title="Descarga el documento antes de firmar">
    `GET /v1/me/contracts/{envelopeID}/pdf` devuelve bytes PDF. Guarda el
    archivo y revisa razón social, dirección, servicios y comisiones. El
    documento ya viene prefirmado por CBPay, pero permanece
    `pending_client` hasta completar la ceremonia.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -o contrato-marco.pdf \
      "https://api.qbank.cl/platform/v1/me/contracts/envelopes/<ENVELOPE_ID>/pdf" \
      -H "Authorization: Bearer <CBPAY_TOKEN>"
    ```
  </Step>

  <Step title="Declara identidad y consentimiento">
    Envía nombre y cargo del firmante en JSON. El consentimiento debe ser el
    booleano JSON `true`. Incluye la firma manuscrita del cliente en
    `signature_png`, como PNG codificado en Base64. Se acepta opcionalmente el
    prefijo `data:image/png;base64,`. El OTP **no** es un campo JSON `code`:
    envía el token fresco de un solo uso en el header `X-OTP-Token`.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST \
      "https://api.qbank.cl/platform/v1/me/contracts/envelopes/<ENVELOPE_ID>/sign" \
      -H "Authorization: Bearer <CBPAY_TOKEN>" \
      -H "Content-Type: application/json" \
      -H "X-OTP-Token: <FRESH_SINGLE_USE_OTP_TOKEN>" \
      -d '{
        "signer_name": "Jordan Lee",
        "signer_title": "Chief Executive Officer",
        "consent": true,
        "signature_png": "<PNG_BASE64_SIGNATURE>"
      }'
    ```
  </Step>

  <Step title="Confirma la finalización">
    La respuesta exitosa devuelve el sobre en `completed`. Vuelve a leer el
    recurso y descarga el PDF para guardar el `final_hash`. El evento
    `contract_envelope_completed` llega a la audiencia org-admin. El claim
    completado también registra automáticamente el hito de Client Journey
    `contract_signed` con actor `system:contract-ceremony`; no es un hito
    manual del admin.
  </Step>
</Steps>

## Imagen de firma del cliente

`signature_png` es obligatorio en toda solicitud de firma. La API acepta PNG
codificado en Base64 con o sin el prefijo `data:image/png;base64,`. Antes de
reclamar el sobre, decodifica y valida la imagen: máximo 700.000 caracteres
Base64, máximo 512 KiB decodificados, dimensiones mínimas de 64×16 píxeles y
al menos 200 píxeles con tinta. Si falla, responde HTTP 422
`invalid_signature_image`; el mensaje identifica la causa de validación.

Después de una firma exitosa, el PNG se incrusta en el bloque de firma del
Cliente en el PDF final. Las respuestas de lista y detalle solo exponen el
booleano `has_client_signature`; nunca devuelven los bytes ni la llave de
storage. Los sobres anteriores sin imagen siguen renderizando el bloque textual.

## Notificación de firma

Al emitir o reemitir un sobre, la cuenta recibe un email de notificación con el
PDF prefirmado adjunto. Cuando está configurada la URL del portal, incluye el
botón **Ir a firmar** y el mismo deep-link `/contracts/{id}` en texto plano.
La entrega del email es best-effort: la fuente de verdad es el recurso y sus
eventos.

## Ejemplos de respuesta

Respuesta de lista:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "items": [
    {
      "id": "7c9e2f1a-4b3c-4d5e-8f60-1a2b3c4d5e6f",
      "account_id": "8d0f1a2b-3c4d-4e5f-9012-6a7b8c9d0e1f",
      "template_version": "v13.3",
      "lang": "es",
      "status": "pending_client",
      "has_client_signature": false,
      "doc_hash": "sha256-of-the-presigned-pdf",
      "cb_signature_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "created_at": "2026-10-01T14:00:00Z",
      "updated_at": "2026-10-01T14:00:00Z"
    }
  ],
  "page": 1,
  "page_size": 50
}
```

Respuesta completada:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "7c9e2f1a-4b3c-4d5e-8f60-1a2b3c4d5e6f",
  "account_id": "8d0f1a2b-3c4d-4e5f-9012-6a7b8c9d0e1f",
  "template_version": "v13.3",
  "lang": "es",
  "status": "completed",
  "has_client_signature": true,
  "doc_hash": "sha256-of-the-presigned-pdf",
  "final_hash": "sha256-of-the-final-pdf",
  "signer_name": "Jordan Lee",
  "signer_title": "Chief Executive Officer",
  "signer_email": "jordan@example.com",
  "signed_at": "2026-10-01T14:03:12Z",
  "otp_channel": "otp",
  "created_at": "2026-10-01T14:00:00Z",
  "updated_at": "2026-10-01T14:03:12Z"
}
```

## Evento de finalización

Suscríbete a `contract_envelope_completed` para actualizar la bandeja del
admin de la organización. El evento es solo org-admin; la cuenta recibe el
PDF final por email y puede leerlo en sus endpoints de sobres.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "event_type": "contract_envelope_completed",
  "account_id": "8d0f1a2b-3c4d-4e5f-9012-6a7b8c9d0e1f",
  "payload": {
    "envelope_id": "7c9e2f1a-4b3c-4d5e-8f60-1a2b3c4d5e6f",
    "final_hash": "sha256-of-the-final-pdf",
    "signer": "Jordan Lee"
  }
}
```

La hora de firma y el idioma siguen disponibles en el detalle del sobre.
No infieras la finalización desde el email: usa el evento y el estado.

## Errores y solución

| HTTP | Código | Causa y solución |
| - | - | - |
| 400 | `idempotency_key_required` | Falta la key en el body o header `Idempotency-Key`; envía una key estable. |
| 400 | `invalid_idempotency_key` | La key supera 256 caracteres; usa una más corta. |
| 403 | `contract_counsel_required` | Falta aprobación de counsel para inglés; aprueba la plantilla o emite `lang=es`. |
| 403 | `human_session_required` | Una API key no puede firmar; usa una sesión humana. |
| 403 | `account_blocked` | La cuenta no está activa; resuelve su estado con un admin. |
| 401 | `invalid_otp` | OTP ausente, vencido o consumido; solicita otro y envíalo en `X-OTP-Token`. |
| 404 | `not_found` | El sobre no existe o pertenece a otra cuenta; usa un ID de tu lista. |
| 409 | `contract_invalid_state` | Ya está completado o voided; no repitas la ceremonia. |
| 422 | `invalid_lang` | Usa exactamente `es` o `en`. |
| 422 | `contract_account_ineligible` | La cuenta no es empresa activa con KYB aprobado. |
| 422 | `contract_unfillable` | Falta un dato verificable o una cobertura de precios; revisa `missing` para `pricing:<flag>`, corrige la fuente y emite un sobre nuevo. |
| 422 | `invalid_consent` | El body debe incluir `"consent": true`. |
| 422 | `invalid_signer` | Nombre y cargo deben ser no vacíos y de máximo 120 caracteres. |
| 422 | `invalid_signature_image` | `signature_png` no es un PNG válido o no está entintado, supera el límite de tamaño codificado/decodificado o no alcanza las dimensiones mínimas; envía una imagen válida y reintenta con un OTP nuevo. |
| 502 | `storage_failed` | Falló el almacenamiento privado; lee y reconcilia el sobre antes de crear otra key. |
| 503 | `storage_unavailable` | Storage privado no disponible; reintenta cuando operaciones lo restaure. |

## Verificación pública del contrato

Todo PDF final lleva en la página de firmas un bloque de verificación: un
código QR con la URL pública del oráculo más un caption con la misma URL y
el hash corto del documento (12 hex). Cualquiera con el código puede
confirmar que el documento es genuino, sin cuenta ni API key.

El `verify_code` viaja en el detalle del sobre (`GET
/v1/me/contracts/envelopes/{envelopeID}`) y en el detalle
organization-admin, así el cliente siempre puede recuperar el link. El
código empieza con `K`, seguida de la identidad del sobre y un HMAC
truncado (`1+32+20` caracteres); no se puede falsificar ni enumerar.

Los navegadores reciben una tarjeta HTML brandeada; los clientes API envían
`Accept: application/json` (o ningún header `Accept`) y reciben JSON:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -H "Accept: application/json" \
  "https://api.qbank.cl/platform/verify/contracts/K3f9a2b7c4d1e5f60817293a4b5c6d7e8f90a1b2c3d4e5f6a7b8c"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "valid": true,
  "type": "contract",
  "status": "ok",
  "raw_status": "completed",
  "org": "CBPay",
  "client": "Acme SpA",
  "template_version": "v13.3",
  "lang": "es",
  "envelope": "7c9e2f1a",
  "doc_hash": "9d4e6f1a8b3c2d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e",
  "final_hash": "c1d2e3f4a5b60718293a4b5c6d7e8f90a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "created_at": "2026-10-05T14:20:00Z",
  "signed_at": "2026-10-05T15:02:11Z",
  "has_cb_signature": true,
  "has_client_signature": true,
  "issued_by": "CBPay"
}
```

El oráculo expone solo hechos de verificación: validez, clase, estado del
sobre, emisor, nombre legal del cliente, versión de plantilla, idioma, ID
corto del sobre, ambos hashes, timestamps y qué firmas están presentes.
Términos, precios, domicilios, emails y cualquier otro dato operativo jamás
salen del servidor. Un código alterado o inventado responde HTTP 404 con el
mensaje genérico `this verification code does not correspond to any
contract issued by this platform`; las peticiones tienen rate-limit por IP
(`429 too_many_attempts`).

| `status` | `raw_status` | Badge (EN / ES / 中文) | Significado |
| - | - | - | - |
| `ok` | `completed` | Signed / Firmado / 已签署 | El acuerdo está firmado y es final; `final_hash` cubre el PDF. |
| `pending` | `pending_client` | Pending signature / Pendiente de firma / 待签署 | El sobre existe y espera la firma del cliente. |
| `failed` | `voided` | Voided / Anulado / 已作废 | El sobre se anuló con motivo; se conserva para auditoría. |

## Preguntas frecuentes

<AccordionGroup>
  <Accordion title="¿Puede firmar una cuenta persona?">
    No. Debe ser una cuenta empresa activa con KYB aprobado y el firmante debe
    ser miembro owner u operator.
  </Accordion>

  <Accordion title="¿Puedo mandar el OTP en el JSON?">
    No. El handler consume un token fresco de un solo uso en `X-OTP-Token`.
    Un campo JSON `code` no forma parte del contrato vigente.
  </Accordion>

  <Accordion title="¿Qué pasa si el PDF tiene un campo vacío?">
    El sobre responde `contract_unfillable`; CBPay nunca emite un PDF firmado
    con un dato obligatorio sin resolver.
  </Accordion>

  <Accordion title="¿Se puede voidar un sobre completado?">
    No. `completed` es terminal. Solo se puede voidar `pending_client` con un
    motivo y luego emitir otro sobre.
  </Accordion>

  <Accordion title="¿Puedo reintentar la firma?">
    Sí, con el mismo sobre y un OTP nuevo. El claim atómico impide una segunda
    firma; un sobre completado responde `contract_invalid_state`.
  </Accordion>

  <Accordion title="El link de verificación responde 404. ¿Se perdió mi contrato?">
    No. HTTP 404 con `this verification code does not correspond to any
            contract issued by this platform` significa que el código se copió mal o
    pertenece a otro sobre. Lee `verify_code` de nuevo desde el detalle del
    sobre o escanea el QR impreso en la página de firmas; el oráculo jamás
    confirma ni niega la existencia de otros sobres.
  </Accordion>

  <Accordion title="¿El código de verificación expira?">
    No. El código sigue válido durante la vida del sobre. Si el sobre se
    anula, el mismo código responde `failed` en vez de `ok`.
  </Accordion>
</AccordionGroup>

## Descarga en ambos idiomas

El detalle del sobre devuelve `available_langs`, empezando por el idioma de emisión — `["es"]` o `["es", "en"]` para un sobre emitido en español (espejado si se emitió en inglés). Este campo es la fuente de verdad de los botones del portal. El ingl?s exige aprobaci?n de counsel y plantilla vigente.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -o master-agreement.pdf "https://api.qbank.cl/platform/v1/me/contracts/envelopes/<ENVELOPE_ID>/pdf?lang=en" -H "Authorization: Bearer <CBPAY_TOKEN>"
```

La respuesta PDF es binaria y usa `contrato-marco-<short>.pdf` en espa?ol o `master-agreement-<short>.pdf` en ingl?s. Pedir el otro idioma renderiza el mismo sobre desde su snapshot congelado; no se almacena ni hashea aparte. La portada identifica el PDF firmado, sus hashes, la prevalencia del espa?ol bajo ?11 y la URL del or?culo. La contrapartida no es un segundo documento firmado.

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "7c9e2f1a-4b3c-4d5e-8f60-1a2b3c4d5e6f",
  "lang": "es",
  "status": "completed",
  "available_langs": ["es", "en"],
  "doc_hash": "sha256-of-the-presigned-pdf",
  "final_hash": "sha256-of-the-final-pdf"
}
```

| HTTP | C?digo | Soluci?n |
| -: | - | - |
| 422 | `invalid_lang` | Usa `lang=es` o `lang=en`. |
| 403 | `contract_counsel_required` | Aprueba counsel antes de pedir ingl?s. |
| 422 | `contract_template_superseded` | Descarga solo el idioma de emisi?n y reemite. |
| 502 | `storage_failed` | Reconcilia el sobre y reintenta; no crees otra ceremonia. |
| 503 | `storage_unavailable` | Recupera el storage privado y reintenta. |

Los emails de emisi?n y firma mencionan el otro idioma del portal solo cuando es servible; sin counsel se omite ingl?s.

### FAQ

<AccordionGroup>
  <Accordion title="?El PDF traducido vale como contrato firmado?">No. Es una contrapartida de lectura. El PDF del idioma de emisi?n sigue siendo el documento firmado y su hash es la referencia.</Accordion>
  <Accordion title="?Por qu? no veo ingl?s?">El ingl?s exige counsel y plantilla vigente. Revisa `available_langs`; el portal habilita los botones desde ese campo.</Accordion>
</AccordionGroup>


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