Referencia de la API de ONP
Este documento describe cómo integrarse con la API de onboarding de Guardline. Cubre la autenticación, la creación de sesiones de onboarding, la declaración de sujetos vinculados (el representante legal para kyc_minor al crear la sesión, los representantes legales de una empresa a mitad del recorrido para kyb), la resolución de enlaces de sesión, los dispensadores de QR reutilizables, el seguimiento del ciclo de vida de la ejecución, la recepción de notificaciones por webhook y el polling de eventos perdidos.
El modelo de integración sigue una separación clara de responsabilidades: Guardline verifica y decide, el integrador orquesta. El integrador declara el onboarding mediante una única llamada a la API, distribuye las URL devueltas en la respuesta (o pide a Guardline que las entregue) y recibe por webhook las notificaciones de progreso y de decisión final.
Descripción general
Sección titulada «Descripción general»En el flujo de integración participan cuatro actores:
| Actor | Responsabilidad |
|---|---|
| Backend del integrador | Crea sesiones, recibe webhooks y consulta ejecuciones |
| API de Guardline | Gestiona sesiones, tokens, ejecuciones, sujetos vinculados y notificaciones |
| Frontend del integrador | Renderiza el enlace directamente o monta un iframe que apunta a él |
| Onboarding de Guardline | Conduce el recorrido de verificación con el usuario final |
Una única llamada a POST /api/v1/onboarding/sessions crea un onboarding para cualquier tipo de flujo. La solicitud lleva los datos del sujeto primario más un arreglo opcional de sujetos vinculados. La respuesta lleva una URL por cada sujeto creado. La notificación de los sujetos vinculados es opcional y se activa por llamada, y sale por los canales configurados para el flujo (SMS y email por defecto, WhatsApp cuando se solicita).
Para kyc_minor, el integrador declara tanto al menor (en primary) como al representante legal (en linked_subjects[]) en la misma solicitud. El recorrido de cada uno es independiente: el menor abre su URL, el representante abre la suya y Guardline orquesta la consolidación. El integrador decide si Guardline entrega la URL del representante por los canales de notificación del flujo (notify.linked: true) o si la entrega queda a su cargo (notify.linked: false u omitido).
Todas las llamadas a la API siguen el patrón REST sobre HTTPS, con payloads JSON y respuestas en el formato de envoltorio estándar de Guardline.
Entornos y autenticación
Sección titulada «Entornos y autenticación»Entornos
Sección titulada «Entornos»| Entorno | URL base | Prefijo de la clave de API |
|---|---|---|
| Sandbox | https://{instance}.onp.dev.guardline.com.br | gl_test_ |
| Producción | https://{instance}.onp.prod.guardline.com.br | gl_live_ |
El valor {instance} se provisiona durante la configuración del integrador e identifica la infraestructura dedicada. Cada instancia tiene su propia base de datos, aplicación y almacenamiento aislados.
Autenticación
Sección titulada «Autenticación»Todas las llamadas a la API dirigidas al integrador requieren la clave correspondiente al entorno. Se aceptan por igual dos formas de encabezado, X-API-Key y Authorization: Bearer. Envíe una de estas:
X-API-Key: gl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
Authorization: Bearer gl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2Las claves se provisionan desde el panel administrativo de Guardline. El token en texto plano se muestra solo en el momento de la creación. Internamente, Guardline almacena el hash SHA-256 de la clave.
El prefijo es una etiqueta elegida al crear la clave, no un mecanismo de enrutamiento, y toma el valor live por defecto cuando no se especifica el entorno. Por eso una clave emitida en una instancia sandbox sin entorno explícito se lee gl_live_ aunque siga siendo una clave exclusiva de sandbox. El prefijo no dice nada sobre qué instancia la aceptará: una clave se autentica únicamente contra la instancia que la emitió.
Eso vale para X-API-Key, cuyo valor siempre se lee como una clave de API. En Authorization: Bearer el prefijo sí enruta: la credencial se trata como clave de API solo cuando empieza por gl_live_ o gl_test_, y cualquier otro valor de ese encabezado se verifica como un JWT. Por eso una clave sin prefijo enviada como Bearer falla la verificación del JWT y responde 401 INVALID_TOKEN. Envíe esa clave en X-API-Key.
Un problema de credenciales siempre responde 401, nunca 400. MISSING_TOKEN cuando no se envió el encabezado, INVALID_API_KEY cuando la clave es desconocida, está revocada o expiró. Un 400 significa que la clave fue aceptada y que se rechazó el cuerpo de la solicitud, por lo que reemitir la clave no lo resolverá.
Las solicitudes HTTP se rechazan. Se exige HTTPS con TLS 1.2 o superior en todos los entornos.
Tres superficies se apartan de esa regla:
| Endpoint | Autenticación |
|---|---|
POST /api/v1/onboarding/executions/{execution_id}/resend-representative-link | JWT de admin o de supervisor emitido por el backoffice de Guardline. Consulte Reenvío del enlace del representante. |
POST y GET /api/v1/onboarding/executions/{execution_id}/representatives | Clave de API, JWT o ninguna credencial: el recorrido KYB los abre desde un enlace mágico y el execution_id de la ruta es la capacidad. Consulte Representantes legales KYB. |
POST /api/v1/onboarding/dispensers/{token}/sessions y GET /api/v1/link/{token} | Públicos. La posesión del token es la capacidad y ambos tienen límite de solicitudes por IP de origen. |
Alcances de la clave
Sección titulada «Alcances de la clave»Una API key puede declarar alcances, y cuando los declara valen para todo el grupo /api/v1/onboarding (que incluye POST /api/v1/onboarding/executions/{execution_id}/cancel) y para POST /api/v1/executions/{execution_id}/cancel. Cada llamada se compara con la primera regla de la tabla que coincide con su verbo y su path:
| Verbo y path | Alcance exigido (basta uno) |
|---|---|
GET .../executions/{execution_id}/documents y .../documents/{document_id}/content | documents:read |
GET .../executions/{execution_id}/identity-verification | identity_evidence:read |
GET /onboarding/notifications | events u onboarding:read |
POST /onboarding/notifications/{delivery_id}/ack | events u onboarding:write |
POST /onboarding/executions/{execution_id}/cancel y POST /executions/{execution_id}/cancel | onboarding:write |
cualquier otro GET /onboarding/... | onboarding:read |
cualquier otro verbo en /onboarding/... | onboarding:write |
Una clave sin alcances declarados, una clave con el comodín * y una llamada con JWT no pasan por esta verificación. Las claves creadas desde el panel de administración llevan onboarding:read y onboarding:write; documents:read se marca aparte (consulte Autenticación y alcance).
identity_evidence:read abre la evidencia sellada de verificación de identidad del trámite (GET /api/v1/onboarding/executions/{execution_id}/identity-verification). onboarding:read no la abre, porque el registro trae la regla de decisión, el puntaje, la dirección de red y los códigos del proveedor. Un execution_id inexistente responde 404; un trámite sin registros responde una lista vacía.
En la práctica, una clave creada solo con onboarding:read recibe 403 en POST /sessions, en las escrituras de legajo, en el aviso de umbral y en la cancelación, y una clave creada solo con documents:read recibe 403 en todo lo que no es documento. El rechazo es 403 FORBIDDEN, y el mensaje termina con los alcances que habrían alcanzado, entre paréntesis y separados por |:
{ "error": true, "message": "credencial sem permissão para esta operação (onboarding:write)", "code": "FORBIDDEN", "request_id": "5b0f8a52-3f7e-4d0c-9a51-1c2e6f7d8a90"}El texto antes de los paréntesis sigue el Accept-Language (el ejemplo es el pt-BR base); los nombres de los alcances no se traducen.
Documento OpenAPI
Sección titulada «Documento OpenAPI»GET /api/openapi.jsonLa especificación de la API, en Swagger 2.0, servida en el propio host de la instancia, con host y schemes reescritos al host que recibió la llamada. Exige la API key; no acepta JWT y no verifica alcances. Los rechazos usan los códigos del middleware de API key, que no son los del grupo /onboarding: 401 MISSING_API_KEY sin credencial, INVALID_API_KEY_FORMAT para un valor fuera de formato e INVALID_API_KEY para una clave desconocida o revocada. La respuesta lleva Cache-Control: no-store.
El documento describe la API entera, incluidas las rutas del panel de administración que una API key no alcanza. Esta página sigue siendo la referencia de lo que un integrador puede llamar.
Idempotencia
Sección titulada «Idempotencia»Los endpoints que mutan estado aceptan el encabezado opcional Idempotency-Key (cualquier cadena única de como máximo 255 caracteres, normalmente un UUID; una más larga devuelve 400 INVALID_IDEMPOTENCY_KEY). Cuando está presente, el servidor cachea la respuesta durante 15 minutos; las llamadas posteriores con la misma clave devuelven la respuesta cacheada con el encabezado de respuesta Idempotency-Replayed: true. Sin el encabezado, cada llamada se procesa de forma independiente.
La caché se indexa por el par (clave, endpoint), por lo que reutilizar una misma clave en dos endpoints distintos no produce colisión. Un endpoint ignora el encabezado a propósito: POST /onboarding/dispensers/{token}/sessions, donde repetir una respuesta cacheada entregaría el mismo recorrido a dos personas.
Úselo al reintentar tras un timeout de red para evitar ejecuciones duplicadas. Genere una clave nueva para cada nueva operación lógica.
Formato de respuesta
Sección titulada «Formato de respuesta»Todas las respuestas siguen el envoltorio estándar:
Éxito:
{ "error": false, "data": {}}Error:
{ "error": true, "message": "descrição do erro", "code": "ERROR_CODE", "request_id": "550e8400-e29b-41d4-a716-446655440000"}El campo message está localizado. Los integradores deben ramificar según el campo code (estable y sin traducir), no según el texto del mensaje.
Las rutas del dispensador son una excepción al envoltorio de éxito: POST /onboarding/dispensers, POST /onboarding/dispensers/{token}/sessions, GET /onboarding/dispensers y las tres escrituras de ciclo de vida (.../deactivate, .../rotate y .../limits) devuelven {"data": ...} sin la clave error cuando tienen éxito. Sus respuestas de error sí llevan "error": true como todas las demás rutas. Trate la ausencia de error como éxito en lugar de comprobar error === false, o las llamadas al dispensador se leerán como fallos.
El campo request_id se devuelve solo en las respuestas de error y sirve para el seguimiento en los tickets de soporte. Una respuesta de éxito lleva error y data, más un arreglo opcional warnings con entradas {code, message, context} que reporta degradaciones no fatales sin convertir la respuesta en un error. warnings se omite cuando no hay nada que reportar.
Idioma de los mensajes de error
Sección titulada «Idioma de los mensajes de error»El campo message se resuelve a partir del encabezado Accept-Language de la solicitud. Quien no envía ningún encabezado recibe el idioma por defecto de la instancia (pt-BR salvo que la instancia esté configurada de otra forma).
Accept-Language | Resultado |
|---|---|
| ausente, no interpretable o sin coincidencia | el idioma por defecto de la instancia (pt-BR salvo que la instancia esté configurada de otra forma) |
pt-BR | pt-BR |
es-AR, es sin región, y las variantes latinoamericanas como es-UY | Español (Argentina) |
es-ES | Español (España) |
en, en-US | Inglés |
En las rutas del recorrido, que llevan una ejecución, la falta del header no cae directo en pt-BR: antes vale el language_code del flujo de esa ejecución. En las rutas del integrador no hay ejecución resuelta antes de la respuesta de error, así que ahí solo cuenta el header.
curl https://{instance}.onp.dev.guardline.com.br/api/v1/link/invalido \ -H "Accept-Language: es-AR"{ "error": true, "message": "Enlace no encontrado", "code": "LINK_NOT_FOUND", "request_id": "77440a78-6e11-4def-8c08-31b1189ccbd4"}El catálogo se indexa por el code del error. Un código sin entrada en el idioma solicitado degrada a su mensaje en pt-BR en lugar de a un valor vacío o a un marcador de posición, de modo que una respuesta nunca queda vacía y el code siempre es la referencia autorizada.
MISSING_TOKEN e INVALID_ID agregan, fuera de pt-BR, una pista técnica entre paréntesis que no se traduce: los encabezados aceptados ((X-API-Key | Authorization: Bearer)) o el nombre del parámetro de ruta que falló ((execution_id)).
La cobertura todavía no está completa y los huecos no siguen al catálogo. La localización se está conectando ruta de respuesta por ruta de respuesta, así que un código puede tener una entrada en español a la que un endpoint concreto nunca llega. Los dos casos siguientes son la misma instancia, el mismo encabezado, la misma solicitud:
POST /sessions { "primary": { "tax_id": "<not a CUIT>" } } -> "message": "Erro de validação" "code": "INVALID_TAX_ID"
POST /sessions { "company": { "tax_id": "<a person's CUIT>" } } -> "message": "El CUIT ingresado no corresponde a una persona jurídica..." "code": "CUIT_NOT_JURIDICAL_PERSON"El primero lo responde una rama local de la ruta que todavía emite su literal en pt-BR; el segundo pasa por el clasificador central, que está totalmente localizado. INVALID_TAX_ID tiene una entrada en español en el catálogo y sencillamente no llega a ella por ese camino. Los fallos de validación de POST /sessions y del endpoint de representantes KYB son las rutas con más probabilidad de responder en pt-BR sin importar el encabezado.
Esta es la razón por la que los integradores nunca deben hacer coincidencias sobre el texto del mensaje, ni construir con él una cadena destinada al usuario final: ramifique según el code y muestre su propio texto.
Recorridos disponibles
Sección titulada «Recorridos disponibles»GET /api/v1/onboarding/flowsDevuelve la lista de recorridos configurados y activos del integrador, con los metadatos necesarios para crear sesiones correctamente.
kyc_representative se filtra del lado del servidor y no aparece: ese hijo se crea únicamente como una entrada de linked_subjects[] de una sesión kyc_minor, y apuntar flow_id a uno de ellos se rechaza con INVALID_FLOW_TYPE.
kyb_representative no se filtra y puede aparecer en esta lista en las instancias que lo configuran. No inicie una sesión contra él. Es el subflujo que un KYB padre provisiona para cada representante declarado (consulte Representantes legales KYB), e iniciarlo directamente produce una ejecución huérfana sin empresa a la que asociarse. Seleccione los recorridos por el campo type en vez de por su posición, y trate kyc, kyb y kyc_minor como el conjunto iniciable.
Respuesta
Sección titulada «Respuesta»{ "error": false, "data": [ { "flow_id": "61000000-0000-0000-0000-000000000001", "flow_type": "kyc", "name": "KYC Pessoa Física", "description": "Verificação de identidade para maiores de 18 anos.", "country_code": "BR", "language_code": "pt-BR", "status": "active", "steps_count": 8, "steps": [ { "id": "cpf", "name": "CPF", "order": 1 }, { "id": "personal_data", "name": "Dados pessoais", "order": 2 }, { "id": "contact", "name": "Informações de contato", "order": 3 }, { "id": "professional_data", "name": "Dados profissionais", "order": 4 }, { "id": "address", "name": "Endereço", "order": 5 }, { "id": "terms", "name": "Termos de uso", "order": 6 }, { "id": "document", "name": "Documentoscopia", "order": 7 }, { "id": "biometric", "name": "Biometria facial", "order": 8 } ], "primary_schema": [ { "field": "full_name", "type": "string", "required": false, "description": "Nome completo" }, { "field": "tax_id", "type": "string", "required": false, "description": "CPF (somente dígitos ou com máscara)" }, { "field": "email", "type": "string", "required": false, "description": "Endereço de e-mail" }, { "field": "phone", "type": "string", "required": false, "description": "Telefone com código do país" }, { "field": "birth_date", "type": "string", "required": false, "description": "Data de nascimento (AAAA-MM-DD)" } ] }, { "flow_id": "61000000-0000-0000-0000-000000000010", "flow_type": "kyc_minor", "name": "KYC Menor de Idade", "description": "Verificação de identidade para menores entre 14 e 17 anos, com etapa adicional do representante legal.", "country_code": "BR", "language_code": "pt-BR", "status": "active", "steps_count": 9, "steps": [ { "id": "cpf_birth_date", "name": "CPF e data de nascimento", "order": 1 }, { "id": "welcome", "name": "Boas-vindas", "order": 2 }, { "id": "personal_data", "name": "Dados pessoais do menor", "order": 3 }, { "id": "contact", "name": "Informações de contato", "order": 4 }, { "id": "professional_data", "name": "Dados profissionais e PEP", "order": 5 }, { "id": "address", "name": "Endereço", "order": 6 }, { "id": "terms", "name": "Termos de uso", "order": 7 }, { "id": "document", "name": "Documentoscopia do menor", "order": 8 }, { "id": "biometric", "name": "Biometria facial do menor", "order": 9 } ], "primary_schema": [ { "field": "full_name", "type": "string", "required": true, "description": "Nome completo do menor" }, { "field": "tax_id", "type": "string", "required": true, "description": "CPF do menor" }, { "field": "birth_date", "type": "string", "required": true, "description": "Data de nascimento do menor (AAAA-MM-DD), idade entre 14 e 17 anos" }, { "field": "phone", "type": "string", "required": false, "description": "Telefone do menor com código do país" }, { "field": "email", "type": "string", "required": false, "description": "Email do menor" } ] } ]}Cada entrada de flujo devuelve también flow_id (la versión concreta del flujo, utilizable como flow_id en POST /sessions) y steps_count. steps_count se lee de la columna propia de la fila del flujo en lugar de contarse a partir del arreglo steps de esa misma respuesta, así que ambos pueden divergir. Cuando el número tenga que ser exacto, tome la longitud de steps.
Cada entrada trae además country_code (ISO 3166-1 alfa-2, el país contra el que se valida primary.tax_id) y language_code (el idioma en que se presenta el recorrido).
El arreglo primary_schema describe los campos aceptados dentro del bloque primary de POST /api/v1/onboarding/sessions. Para kyc_minor, el integrador declara además al representante legal dentro de linked_subjects[] (consulte Sujetos vinculados). El recorrido del representante se ejecuta de forma independiente del recorrido del menor; el steps anterior enumera solo los pasos del menor.
Los flujos KYB devuelven además company_schema, un arreglo con la misma forma que describe los campos aceptados dentro del bloque company (consulte Prellenado de la empresa (KYB)). Está ausente en los flujos de personas, que no tienen sujeto empresa. Lea el esquema en lugar de fijar la lista de campos en el código: es la declaración del propio servidor sobre lo que acepta el flujo.
La entrada tax_id de primary_schema sigue el país del flujo: el CPF en flujos BR, el DNI o CUIT/CUIL en flujos AR (y, en un flujo que identifica a la persona por su DNI, la reducción descrita en Tax ID según el país del flujo), la cédula en flujos UY. Las descripciones están en portugués.
Requisitos de información mínima
Sección titulada «Requisitos de información mínima»GET /api/v1/onboarding/requirements publica el conjunto de información mínima
que hay que recabar, por tipo de cliente y clasificación. La aplicación del
integrador lo consulta antes de abrir la sesión, para saber qué pedir.
Parámetros de consulta
Sección titulada «Parámetros de consulta»| Parámetro | Valores | Obligatorio | Descripción |
|---|---|---|---|
customer_type | person, company | No | Restringe a un tipo de cliente. |
classification | ocasional, habitual | No | Restringe a una clasificación. |
version | entero positivo | No | Fija una versión publicada. Exige customer_type y classification juntos: sin ellos no hay un conjunto único que fijar. |
Sin parámetros, la respuesta trae los conjuntos activos. La colección no está paginada: un tenant publica un conjunto por par (tipo de cliente, clasificación), así que son pocas filas, y paginar inventaría metadatos que no le sirven a nadie.
Respuesta (200)
Sección titulada «Respuesta (200)»Cada conjunto trae customer_type, classification, version,
effective_from, items_count e items. Cuando un conjunto extiende a otro,
inherits_from nombra la clasificación y la versión de la base, y la base ya
viene fusionada en los ítems.
Cada ítem trae:
| Campo | Descripción |
|---|---|
code | Clave estable de máquina. El ruteo se hace sobre ella. |
label | El texto del propio documento rector del cliente. La plataforma no lo traduce, porque un auditor compara la cadena contra la sección de origen. |
data_type | Tipo de dato esperado. |
required | Si el ítem es obligatorio. |
order | Posición dentro del conjunto. |
source | La posición numerada en el documento rector, que es lo que le permite a Cumplimiento reconciliar una respuesta contra su propia lista. |
description | Detalle opcional. |
inherited | Marca un ítem que viene del conjunto base y no del conjunto pedido. Existe para que el integrador pueda renderizar el bloque adicional por separado, sin tener que traer los dos conjuntos y compararlos. |
Versionado
Sección titulada «Versionado»version es la versión de la parametrización, no de la API. Cambia cuando
Cumplimiento republica el conjunto. Un integrador puede fijarla en la petición y
seguir leyendo la misma respuesta después de una republicación.
effective_from es el momento en que se publicó esa versión, y es estable
ante una republicación de la base: la herencia apunta a una fila base concreta,
así que publicar ocasional v2 no altera en silencio el conjunto habitual que
hereda ocasional v1. Hacer que el conjunto habitual siga a la base nueva es un
acto deliberado, una versión nueva del habitual. Esa es la propiedad que impide
que una reparametrización rompa la aplicación ya en producción.
Errores
Sección titulada «Errores»| HTTP | code | Cuándo |
|---|---|---|
| 400 | INVALID_CUSTOMER_TYPE | customer_type fuera de person y company. |
| 400 | INVALID_CLASSIFICATION | classification fuera de ocasional y habitual. |
| 400 | INVALID_REQUIREMENT_VERSION | version no es entero positivo, o llegó sin customer_type y classification. |
| 404 | REQUIREMENT_SET_NOT_FOUND | Consulta restringida sin conjunto publicado. |
El 404 es deliberado, en lugar de un 200 con lista vacía: quien pregunta está consultando el mínimo legal de una entidad supervisada, y una lista vacía se lee como “no se exige nada”, que es la respuesta equivocada que más se parece a una correcta.
Creación de una sesión
Sección titulada «Creación de una sesión»POST /api/v1/onboarding/sessionsCrea una nueva sesión de onboarding y devuelve una URL por sujeto. Para kyc_minor, la respuesta incluye una URL aparte para el representante vinculado junto a la URL del menor. Para kyb, los representantes legales pueden declararse aquí cuando ya se conocen; cuando su cantidad y su identidad solo se conocen a mitad del recorrido, se declaran en el paso de configuración de representantes (consulte Representantes legales KYB).
Esquema de la solicitud
Sección titulada «Esquema de la solicitud»| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
flow_type | string | Uno de flow_type o flow_id | Tipo de recorrido. Valores posibles: kyc, kyb, kyc_minor. Solo se resuelve cuando la instancia tiene un único flujo activo de ese tipo; con más de uno, responde 400 FLOW_ID_REQUIRED. Prefiera siempre flow_id. |
flow_id | UUID | Uno de flow_type o flow_id | Versión concreta del flujo. Úselo para fijar una configuración de flujo determinada cuando existen varios flujos del mismo tipo. La resolución a un flujo cuyo tipo es de uso interno (como kyc_representative) se rechaza con INVALID_FLOW_TYPE. |
channel | enum | No | Origen de la sesión: web, sdk, api, asistido_agencia. Por defecto: web. También se acepta el alias heredado channel_code; cuando se envían ambos, gana channel. asistido_agencia marca una sesión abierta por un funcionario de sucursal en nombre del solicitante y solo tiene sentido en las instancias configuradas para ello. |
classification | enum | No | Clasificación del cliente en el eje ocasional / habitual del régimen uruguayo. El integrador la declara y la plataforma la registra; no la deriva, porque no ve ninguna transacción. Ausente deja la sesión sin clasificar, que es un estado distinto de los dos valores y el estado normal fuera de Uruguay. Se devuelve en la consulta de la ejecución. |
layout_mode | enum | No | Diseño de la interfaz del recorrido: stepper o chat. No hay valor por defecto del lado de la API: cuando se omite el campo, el recorrido se renderiza con el diseño configurado en el propio flujo. |
reference_id | string (≤ 255) | No | Correlacionador de formato libre para el sistema del integrador. Se devuelve en los webhooks y se puede consultar por API. Recomendado: un identificador único y estable, como el ID de la cuenta o el de la solicitud de onboarding. |
parent_execution_id | UUID | No | Vincula esta ejecución a una ejecución padre. Reservado para casos de uso avanzados, como los subflujos KYB; no es necesario para los recorridos estándar kyc/kyc_minor/kyb. |
metadata | object | No | Paso a través de formato libre. Se almacena junto con la sesión y ningún endpoint de lectura lo devuelve, así que es de solo escritura desde el lado del integrador. Use reference_id para un correlacionador que deba poder leerse de vuelta. |
surface_hint | enum | No | Póngalo en embed cuando el integrador vaya a renderizar el recorrido dentro de un iframe. Determina la sanitización de PII en GET /api/v1/link/{token}. Consulte Surface hint (modo embed). |
redirect_url | string (≤ 2048) | No | URL https absoluta a la que el recorrido envía a la persona después de la pantalla final, para que el integrador recupere a su usuario dentro de su propia aplicación. Se rechaza con 422 UNPROCESSABLE cuando el esquema no es https o falta el host; los valores javascript:, data: y http:// se rechazan en la creación. Se ignora en modo embed, donde el puente del embed publica dismissed y la página anfitriona controla la navegación. Sin valor, la pantalla final no ofrece salida. |
primary | object | Obligatorio para kyc_minor; opcional para kyc y kyb | Datos del sujeto primario. Los campos se describen en el primary_schema devuelto por GET /flows. Puede omitirse (cold-start) en kyc y kyb, en cuyo caso el registro del cliente se materializa cuando el usuario envía el paso de documento del recorrido. primary.tax_id se valida contra el país del flujo, consulte Tax ID según el país del flujo. Esa comprobación también se ejecuta en KYB, antes de que el bloque se descarte, así que un primary.tax_id malformado devuelve 400 en una solicitud KYB aunque el valor nunca se hubiera persistido. |
company | object | No; solo en los flujos KYB de Argentina | Prellenado de la empresa. Consulte Prellenado de la empresa (KYB). Se descarta en los flujos de personas: el sujeto de una ejecución KYB es una empresa, y los sujetos persona y empresa son mutuamente excluyentes en una ejecución. También se descarta, en silencio y sin error, cuando el país del flujo KYB resuelto no es AR. |
linked_subjects | array | Obligatorio para kyc_minor (exactamente una entrada, role: "representative"); opcional en kyb (una entrada por representante legal, role: "legal_representative"); vacío en los demás tipos de flujo | Sujetos vinculados al primario. Consulte Sujetos vinculados. |
notify | object | No | Banderas de adhesión a las notificaciones. Consulte Adhesión a las notificaciones. |
enrollment | object | No; solo flujos de alta simplificada | Datos mínimos del alta, por paso. Consulte Alta simplificada. |
operation | object | No; cuando se envía, direction y wallet_type son obligatorios | Sentido y tipo de wallet de la primera operación del cliente, registrados solo en flujos de Uruguay e ignorados en los demás. Consulte Operación que abrió el onboarding. |
Tax ID según el país del flujo
Sección titulada «Tax ID según el país del flujo»primary.tax_id se valida contra el país que declara el flujo resuelto, no contra el flow_type crudo de la solicitud. El país proviene del registro del flujo, de modo que el mismo contrato de API sirve a las instancias brasileñas y a las argentinas sin un interruptor por tenant.
| País del flujo | Se acepta en primary.tax_id |
|---|---|
BR | Un CPF válido (11 dígitos, con los dígitos verificadores comprobados). |
AR | Un CUIT/CUIL de 11 dígitos con el dígito verificador comprobado, o un DNI de 7 a 8 dígitos. Las letras se rechazan. En un flujo que identifica a la persona por su DNI (settings.primary_tax_id_require_dni), un CUIT/CUIL de persona humana (prefijo 20, 23, 24, 25, 26 o 27, con un DNI de al menos 7 dígitos) se reduce a ese DNI, que identifica al cliente, y se guarda como tax_number del cliente. Allí un CUIT de empresa (30/33/34) se rechaza con INVALID_TAX_ID. |
| otro | La forma de identificador extranjero: de 4 a 20 caracteres, letras, dígitos y guiones, en mayúsculas y sin espacios, puntos ni barras. Nunca se reduce a dígitos. |
Envíe primary.tax_id solo con dígitos. El campo no se normaliza. En un flujo brasileño la comprobación se ejecuta sobre la cadena cruda y exige exactamente 11 caracteres numéricos, así que un CPF con máscara como 123.456.789-09 se rechaza. En un flujo argentino el valor canónico se calcula para la comprobación y luego se descarta, así que la cadena llega a la base de datos exactamente como se envió, y 20-12345678-6 y 20123456786 se convierten en dos registros de cliente distintos para una misma persona. Excepción: en un flujo que identifica a la persona por su DNI, el DNI, el DNI completado con ceros y cada CUIL de persona que lo contiene resuelven al mismo cliente, sea cual sea la escritura guardada primero. Una discrepancia devuelve 400 INVALID_TAX_ID.
company.tax_id es la excepción: ese campo sí se normaliza y allí se acepta la máscara (consulte Prellenado de la empresa (KYB)).
Dos consecuencias que conviene prever:
- La comprobación se ejecuta después de resolver el flujo, y
flow_typesolo se resuelve cuando la instancia tiene exactamente un flujo activo de ese tipo. Una instancia provisionada suele llevar los flujos base de Guardline junto con los propios del tenant, así que lo habitual es que haya más de uno. En ese caso la solicitud sinflow_idresponde 400FLOW_ID_REQUIRED, con un mensaje que apunta aGET /api/v1/onboarding/flows, en lugar de enviarse a un flujo de otro país. - Envíe siempre
flow_id. Léalo una vez desdeGET /flowsy fíjelo. Trateflow_typecomo una comodidad reservada a las instancias de un solo flujo. - Un flujo sin país explícito recae en
BR, que es el lado estricto.
Prellenado de la empresa (KYB)
Sección titulada «Prellenado de la empresa (KYB)»En un flujo KYB el sujeto de la ejecución es una empresa, así que primary no tiene a qué asociarse y se descarta. El bloque company es la contraparte KYB: abre el recorrido con el tax ID que el integrador ya posee en lugar de volver a pedírselo al solicitante.
El bloque solo se aplica cuando el flujo resuelto es un flujo KYB cuyo país es Argentina. En cualquier otro caso, incluido un flujo KYB brasileño, se descarta sin decir nada: la sesión se crea, la respuesta es 201 y nada en ella indica que el prellenado se ignoró. Verifique el país del flujo antes de confiar en él.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
company.tax_id | string | No | CUIT de la empresa, con o sin máscara. Debe pertenecer a una persona jurídica: prefijos AFIP 30, 33 o 34. Cualquier otro valor devuelve 422 CUIT_NOT_JURIDICAL_PERSON en lugar de materializar un registro de empresa para un ser humano. |
company.razon_social | string (≤ 255) | No | Razón social registrada, sujeta a la misma lista blanca de texto libre que full_name. Opcional: tanto la consulta al padrón como el OCR del estatuto la completan más adelante, y ninguno de los dos sobrescribe un nombre suministrado por el integrador. |
Las empresas se reutilizan por CUIT igual que las personas se reutilizan por tax ID (consulte Semántica de reutilización del CPF). Si ya existe un registro de empresa para el CUIT, la sesión se vincula a él y conserva el nombre almacenado: la razon_social de la solicitud solo siembra un registro que se crea por primera vez. No espere que una sesión posterior corrija un nombre ya registrado.
{ "flow_id": "61000000-0000-0000-0000-000000000092", "company": { "tax_id": "30712345671", "razon_social": "Estancias y Colonias Arizona S.A." }, "reference_id": "KYB-2026-00042"}Un CUIT suministrado aquí queda bloqueado durante el resto del recorrido: el solicitante no puede editarlo en el paso de la empresa y el intento devuelve COMPANY_TAX_ID_LOCKED. Si el número es incorrecto, emita una sesión nueva en lugar de intentar corregirlo sobre la marcha.
La verificación del formato del documento de la empresa dentro del recorrido
sigue el documento que el propio flujo declara, y no una regla argentina fija. Un
flujo cuyo paso de empresa declara CUIT conserva la regla de persona jurídica
(30, 33 o 34) y rechaza con 422 CUIT_NOT_JURIDICAL_PERSON. Un flujo que
declara otro documento, como el RUT uruguayo de doce dígitos y sin convención de
prefijo, rechaza con 422 COMPANY_DOCUMENT_INVALID, el mismo estado sin la
instrucción de empezar con 30, 33 o 34, porque esa instrucción nombraría una regla
que en ese flujo no existe. Un flujo que no declara nada conserva la regla del
CUIT. El bloque company de POST /sessions, en cambio, sigue restringido al KYB
argentino.
Sujetos vinculados
Sección titulada «Sujetos vinculados»El arreglo linked_subjects declara cualquier sujeto vinculado al primario por el contrato del flujo. Hoy esto cubre al representante legal de kyc_minor y a los representantes legales de una empresa kyb. En KYB, cada entrada con tax_id y full_name crea la ejecución hija del representante y, en los flujos KYB configurados para ello, vincula a la persona como representante legal de la empresa (si la empresa todavía no está vinculada a la sesión, el vínculo se escribe cuando se vincula); los representantes también pueden declararse a mitad del recorrido, consulte Representantes legales KYB.
Cada entrada tiene:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
role | enum | Sí | Discriminador de rol: representative o legal_representative. kyc_minor exige exactamente una entrada con representative; en kyb, cada representante legal usa legal_representative. Otros valores (como officer) están reservados y se rechazan. |
subject | object | Sí | Bloque de datos del sujeto. Puede estar vacío ({}) para una creación shell. |
El bloque subject acepta los siguientes campos:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
full_name | string | Emparejado con tax_id (consulte Obligatoriedad de campos, más abajo) | Nombre completo. |
tax_id | string | Emparejado con full_name | Tax ID personal. Este campo nunca se valida: se acepta cualquier cadena y la sesión se crea igualmente con 201. Es responsabilidad de quien llama enviarlo normalizado, solo con dígitos y con la forma que emite el país del flujo (consulte Tax ID según el país del flujo); un valor con formato crea un cliente duplicado indexado por el texto con formato. |
phone | string (E.164) | No | Teléfono con código de país. notify.linked no lo exige: el envío necesita phone o email, y solo se detiene cuando faltan ambos. Se valida contra la política de teléfonos del flujo: un flujo mobile-only rechaza los fijos con 422 PHONE_POLICY_VIOLATION. |
birth_date | string (YYYY-MM-DD) | No | Fecha de nacimiento. Cuando se proporciona, debe indicar 18 años o más; un representante más joven se rechaza con 422 REPRESENTATIVE_AGE_BELOW_MINIMUM. |
email | string | No | Email. |
relationship | enum | Obligatorio para kyc_minor; opcional en caso contrario | Relación del representante legal con el menor. Obligatorio al crear la sesión para kyc_minor, con independencia de que el resto de subject sea un shell. Valores: mother, father, grandparent, legal_guardian, tutor, other. Consulte Cardinalidad de kyc_minor. |
notification_channel | enum | No | Canal preferido para la entrega gestionada por el backend: sms, whatsapp o email_sms. |
Obligatoriedad de campos en linked_subjects[i].subject
Sección titulada «Obligatoriedad de campos en linked_subjects[i].subject»El servicio acepta tres formas:
| Forma | Contenido | Efecto |
|---|---|---|
| Shell | subject sin tax_id y full_name a la vez (en kyc_minor sigue llevando relationship) | Crea la ejecución vinculada sin materializar un registro de cliente. El integrador entrega la URL manualmente. |
| Mixta | subject: {tax_id: "..."} o subject: {full_name: "..."} (solo uno de los dos) | Se acepta; se trata como shell. El campo aislado no dispara la materialización del cliente. No es un error. |
| Materializada | subject: {tax_id: "...", full_name: "...", ...} (ambos presentes) | Crea o reutiliza un registro de cliente por tax_id y lo enlaza de vuelta a la ejecución vinculada. Los demás campos del sujeto (email, phone, birth_date) rellenan el nuevo registro de cliente en el momento de la creación; se ignoran cuando el cliente ya existe (consulte Semántica de reutilización del CPF). |
Con independencia de la forma, notify.linked: true necesita al menos un campo de contacto en el sujeto: phone, email o ambos. Cada canal se omite cuando su propio campo de destinatario está vacío, y el envío se detiene antes de llamar a ningún proveedor solo cuando faltan los dos, en cuyo caso notify_sent se registra como false. A un representante que solo tiene email se lo notifica por la vía del email.
Para flow_type: kyc_minor, subject.relationship es obligatorio al crear la sesión, sea cual sea la forma anterior que aplique. Un shell (subject: {}) sin relationship se rechaza; consulte Cardinalidad de kyc_minor.
Cardinalidad de kyc_minor
Sección titulada «Cardinalidad de kyc_minor»Para flow_type: kyc_minor, el servicio exige exactamente una entrada en linked_subjects con role: "representative". Cualquiera de los siguientes casos devuelve 400 LINKED_SUBJECTS_REQUIRED:
- campo
linked_subjectsausente linked_subjects: [](arreglo vacío)- Más de una entrada
- Una entrada con un
roledistinto derepresentative
La entrada también debe llevar subject.relationship. La ausencia de subject.relationship devuelve 422 LINKED_SUBJECT_RELATIONSHIP_REQUIRED. La relación es la fuente canónica del tipo de responsable que consumen más adelante el bloqueo del documento de vínculo y el salto automático de la etapa de relación dentro del recorrido, así que debe declararse desde el principio, incluso cuando el resto del sujeto es un shell.
La creación shell sigue siendo explícita y posible; basta con incluir relationship: envíe linked_subjects: [{role: "representative", subject: {relationship: "mother"}}].
Para kyc_minor, se aplican dos validaciones adicionales a la entrada del representante:
linked_subjects[0].subject.relationshipes obligatorio. Omitirlo devuelve 422LINKED_SUBJECT_RELATIONSHIP_REQUIRED.- El
tax_iddel representante debe ser distinto deltax_iddel menor. Reutilizar el mismo CPF devuelve 422CPF_MINOR_REPRESENTATIVE_MATCH.
Adhesión a las notificaciones
Sección titulada «Adhesión a las notificaciones»El bloque notify controla si Guardline entrega la URL por su cuenta, por SMS, email o WhatsApp.
| Campo | Tipo | Por defecto | Descripción |
|---|---|---|---|
notify.primary | bool | false | Reservado. Actualmente no tiene efecto; versiones futuras enviarán la URL del primario. |
notify.linked | bool | false | Cuando es true, Guardline registra la adhesión y envía la URL del representante cuando el menor termina su módulo (no al crear la sesión). El plan por defecto es SMS más email; WhatsApp se usa solo cuando subject.notification_channel lo nombra. La adhesión se aplica al representante de kyc_minor y necesita subject.phone, subject.email o ambos. |
Momento del envío
Sección titulada «Momento del envío»Para kyc_minor, el representante solo se necesita después de que el menor completa su recorrido. Guardline persiste la adhesión notify.linked en POST /sessions y dispara el mensaje en ese momento posterior (cuando el padre pasa a pending_representative), no de forma síncrona durante la creación de la sesión. Por eso, el campo linked_subjects[i].notify_sent de la respuesta de POST /sessions siempre es false. El resultado real del envío aparece más tarde en representative_notify_sent de GET /api/v1/link/{token} para el token del padre (leído de la fila de la ejecución hija).
Selección del canal
Sección titulada «Selección del canal»Cuando se dispara el envío, los canales se seleccionan así:
linked_subjects[i].subject.notification_channelcuando está presente:smsenvía solo SMS,whatsappenvía solo WhatsApp,email_smsenvía SMS y email.- En caso contrario se aplica el plan por defecto, que es el mismo conjunto que
email_sms: SMS y email. WhatsApp nunca forma parte de la alternativa y solo se dispara cuando se lo pide por su nombre.
Después, cada vía se omite cuando su propio campo de destinatario está vacío: SMS y WhatsApp necesitan subject.phone, el email necesita subject.email.
Semántica de notify_sent
Sección titulada «Semántica de notify_sent»El campo linked_subjects[i].notify_sent de la respuesta de POST /sessions siempre es false, porque el envío se difiere hasta que el menor termina (consulte Momento del envío). No indica si se envió algún mensaje.
Para observar el resultado real del envío, lea representative_notify_sent en GET /api/v1/link/{token}:
true: al menos uno de los canales intentados fue aceptado por su proveedor. En SMS y WhatsApp eso significa que el proveedor de mensajería devolvió un identificador de mensaje; en email significa que el proveedor de correo aceptó el mensaje para su entrega.false:notify.linkedestaba desactivado, el sujeto no llevaba niphoneniemail, o todos los canales intentados fueron rechazados.
Un ID de mensaje del proveedor no significa que el mensaje se haya entregado al destinatario. La confirmación asíncrona de entrega (callbacks de estado del proveedor) no se expone en esta API; permanece en los registros del log de comunicaciones accesibles desde el panel administrativo. Es una decisión deliberada para mantener la superficie de la API honesta y pequeña.
Surface hint (modo embed)
Sección titulada «Surface hint (modo embed)»Los integradores que renderizan el recorrido dentro de un iframe envían surface_hint: "embed" en la solicitud. El backend persiste la bandera en la ejecución. Cuando el iframe carga /api/v1/link/{token} para renderizar el recorrido, la respuesta se sanitiza (se eliminan CPF, email, phone y birth_date del bloque del cliente; se eliminan los campos de URL del representante del payload del padre). Consulte Comportamiento de la sanitización de PII.
El valor de surface_hint es interno del servidor y no se devuelve en ninguna respuesta (ni en /sessions ni en /link/{token}). Los integradores observan su efecto únicamente por la forma sanitizada del bloque customer en /link/{token}.
El endpoint heredado GET /api/v1/embed/{token}, que servía como resolvedor sanitizado aparte, se eliminó. Los integradores de embed ahora usan /api/v1/link/{token} tras crear la sesión con surface_hint: "embed".
Semántica de reutilización del CPF
Sección titulada «Semántica de reutilización del CPF»Cuando primary.tax_id coincide con un registro activo existente en customers_person, el servicio reutiliza el cliente existente en lugar de crear uno nuevo. En ese caso, los campos primary.full_name, primary.email, primary.phone y primary.birth_date de la solicitud se ignoran; el cliente conserva los datos de su primera creación.
Lo mismo se aplica a linked_subjects[i].subject.tax_id cuando el CPF del sujeto vinculado coincide con un registro existente.
La coincidencia es sobre la cadena exacta enviada, no sobre una forma normalizada, porque ninguno de los dos campos se normaliza. Dos escrituras del mismo documento son dos claves distintas y producen dos registros de cliente distintos, así que la reutilización solo funciona si el integrador envía solo dígitos y los envía siempre de la misma manera. Esto golpea allí donde puede colarse un valor con formato: linked_subjects[i].subject.tax_id, que nunca se valida, y primary.tax_id en un flujo argentino. Excepción: en un flujo que identifica a la persona por su DNI, el DNI, el DNI completado con ceros y cada CUIL de persona que lo contiene resuelven al mismo cliente, sea cual sea la escritura guardada primero.
Motivo: idempotencia. Reintentar con el mismo CPF no duplica registros de clientes. Contrapartida: los datos del cliente quedan fijados a la primera creación; las correcciones mediante llamadas posteriores a POST /sessions no se propagan.
Para actualizar el nombre, el email o el teléfono de un cliente existente, use el backoffice o los endpoints de gestión de clientes; POST /sessions no es la vía para actualizar clientes. En desarrollo local, use un CPF nuevo por prueba o elimine el registro existente antes de reutilizar el CPF.
Política de cliente ya registrado
Sección titulada «Política de cliente ya registrado»El comportamiento por defecto, y el de toda instancia que no configura nada, es el
descrito en Semántica de reutilización del CPF:
el legajo existente se reutiliza y un segundo recorrido es una segunda
ejecución sobre un cliente. Ese comportamiento es el que sostiene la
actualización periódica del legajo y el step-up de ocasional a habitual, así
que un flujo sin configurar tiene que conservarlo.
Un flujo puede declarar lo contrario. Cuando el paso de identidad del flujo (el
paso de CPF o su equivalente localizado de documento) lleva
blockRegisteredCustomer: true en su configuración, un alta nueva se rechaza con
409 CUSTOMER_ALREADY_REGISTERED. Solo el booleano JSON true activa la
política; cualquier otro valor, una configuración ilegible o una falla de lectura
mantienen la reutilización, porque de las dos direcciones de falla la que bloquea
solicitantes vivos es la que no vale la pena arriesgar.
Qué cuenta como ya registrado. El estado del legajo, y solo tres de ellos:
| Estado del legajo | ¿Rechaza? | Por qué |
|---|---|---|
approved, active, blocked | Sí | El alta terminó. |
pending | No | Es un solicitante en curso. Bloquearlo dejaría varado a quien abandonó un recorrido y volvió, que es el caso más común que existe. |
rejected | No | Un rechazo es una decisión de cumplimiento, no un descuido: quien corrigió su propio legajo necesita una puerta. |
inactive | No | Es el fin de la relación comercial, un eje distinto del veredicto de KYC. Quien fue dado de baja hace años y pide registrarse otra vez está haciendo un alta nueva. |
Dónde actúa la política y dónde nunca actúa. Cubre las dos puertas por las que el solicitante entra por documento: la creación de sesión con prellenado y el paso de identidad de un recorrido abierto sin prellenado. Deliberadamente no actúa en tres lugares:
- Recorridos abiertos por el id del cliente (Abrir un recorrido para un cliente que ya existe). Nombrar la fila es la forma de decir “completá este legajo”, que es el pedido opuesto a “registrame”. Es la puerta por la que entra el step-up.
- Flujos de representante (
kyb_representative,kyc_representative). La persona que recorre el subflujo está ahí para ser identificada en el legajo de otra persona, y rechazarla significaría que una empresa cuyo representante legal ya es cliente nunca podría darse de alta. - Empresas. La política se lee del paso de identidad de persona.
Operación que abrió el onboarding
Sección titulada «Operación que abrió el onboarding»En los flujos de Uruguay, la matriz de riesgo puntúa el factor Servicios con la primera operación que pidió el cliente: el sentido de la operación cruzado con el tipo de wallet. La plataforma no ve esa operación, y ninguna pantalla del recorrido la pregunta, así que llega en la creación de la sesión dentro del bloque opcional operation.
{ "flow_id": "61000000-0000-0000-0000-000000000120", "classification": "ocasional", "operation": { "direction": "venta", "wallet_type": "centralized" }}| Campo | Valores | Descripción |
|---|---|---|
operation.direction | venta, compra | Sentido visto desde la casa: venta cuando vende activos virtuales al cliente (el cliente compra), compra cuando compra activos virtuales al cliente (el cliente vende). |
operation.wallet_type | centralized, decentralized | La wallet del cliente en esa operación: alojada en un exchange (centralized) o autocustodiada (decentralized). Mismo vocabulario que el campo walletType del monitoreo transaccional. |
Reglas del bloque:
- Los valores son literales y en minúscula.
COMPRA,centralizada,buyoINse rechazan. - Los dos campos son obligatorios cuando se envía el bloque. Un bloque al que le falta uno, o
{}, responde 400MISSING_REQUIRED_FIELDS; un valor fuera de la lista responde 400VALIDATION_FAILED."operation": nullequivale a no enviar el bloque. - Solo los flujos de Uruguay registran el bloque. En un flujo de otro país se acepta y se descarta en silencio, sin error y sin
warnings. - Sin el bloque, el factor Servicios no puntúa. No se deriva ni se asume nada: el factor queda fuera del cálculo de esa ejecución. Envíe el bloque solo cuando hay una operación detrás de la sesión; un recorrido abierto sin operación debe seguir sin él.
- Es de solo escritura. El bloque queda registrado en la sesión y ningún endpoint de lectura lo devuelve, tampoco
GET /executions/{execution_id}. Un valor con el mismo nombre enviado como dato de paso por el recorrido se descarta antes de puntuar. - Cada ejecución puntúa con el bloque de su propia sesión. Abrir un recorrido para un cliente que ya existe acepta el mismo bloque; un recorrido de escalón abierto sin él queda sin factor Servicios. En esa ruta, sin embargo, el bloque no pasa por la validación de campos: un bloque incompleto o con un valor fuera de la lista no responde 400, se ignora sin aviso. Valide el bloque de su lado antes de enviarlo.
Los mensajes de esos dos 400 salen de la rama local de la ruta descrita en Idioma de los mensajes de error, así que pueden llegar en pt-BR cualquiera sea el Accept-Language, y no indican qué campo falló. Ramifique por code.
Alta simplificada
Sección titulada «Alta simplificada»Un flujo configurado como alta simplificada acepta, en la misma creación de la sesión, los datos mínimos del alta en el bloque enrollment, y devuelve la primera calificación de riesgo en la respuesta. En cualquier otro flujo el bloque se ignora.
{ "flow_id": "61000000-0000-0000-0000-000000000130", "enrollment": { "steps": { "name-ar": { "...": "..." }, "document-number-ar": { "...": "..." } } }}enrollment.steps asocia el código de cada paso del flujo con el payload que ese paso recibiría desde su pantalla. Cada entrada se registra como paso completado, en el orden del flujo. El conjunto tiene que ser exactamente el conjunto mínimo que declara el flujo: un paso fuera de ese conjunto, un paso mínimo ausente o vacío, un paso que solo puede completar la persona, o un flujo de empresa responden 400 VALIDATION_FAILED, y no queda ninguna ejecución creada. El mensaje es genérico y no indica qué paso falló.
En la respuesta 201, data.risk trae la calificación calculada en el alta, sin el puntaje numérico, que se retiene como en toda lectura del integrador:
| Campo | Descripción |
|---|---|
stage | alta_masiva. |
version | Versión de la calificación. |
status | scored o exempt. |
risk_level | low, medium, high o critical. Ausente cuando es exempt. |
band | bajo, medio o alto. Ausente cuando es exempt. |
Es una calificación, no una decisión: el caso no está decidido. data.risk se omite en toda sesión que no es de alta simplificada.
Ejemplos
Sección titulada «Ejemplos»KYC estándar, cold-start (sin datos en primary)
Sección titulada «KYC estándar, cold-start (sin datos en primary)»{ "flow_type": "kyc", "channel": "web", "reference_id": "SOL-2026-00099"}El registro del cliente se materializa cuando el usuario envía el paso de documento del recorrido. Hasta ese momento, customer_id está ausente de la respuesta.
KYC estándar, con datos completos en primary
Sección titulada «KYC estándar, con datos completos en primary»{ "flow_type": "kyc", "channel": "web", "reference_id": "SOL-2026-00099", "primary": { "full_name": "Carlos Oliveira", "tax_id": "12345678909", "email": "carlos@exemplo.com", "phone": "+5511999990000", "birth_date": "1990-05-15" }}KYC Minor con datos completos y notificación entregada por Guardline
Sección titulada «KYC Minor con datos completos y notificación entregada por Guardline»{ "flow_type": "kyc_minor", "channel": "web", "reference_id": "SOL-2026-00042", "primary": { "full_name": "João Santos", "tax_id": "12345678909", "birth_date": "2010-05-15" }, "linked_subjects": [ { "role": "representative", "subject": { "full_name": "Maria Santos", "tax_id": "98765432100", "phone": "+5511999998888", "email": "maria@exemplo.com", "birth_date": "1985-03-20", "relationship": "mother", "notification_channel": "sms" } } ], "notify": { "primary": false, "linked": true }}Guardline crea ambas ejecuciones y registra la adhesión notify.linked. El SMS a +5511999998888 con la URL del representante se envía cuando el menor termina su módulo, no al crear la sesión; por eso la respuesta de POST /sessions devuelve notify_sent: false (consulte Momento del envío).
KYC Minor con representante shell (el integrador entrega el enlace)
Sección titulada «KYC Minor con representante shell (el integrador entrega el enlace)»{ "flow_type": "kyc_minor", "reference_id": "SOL-2026-00043", "primary": { "full_name": "Ana Pereira", "tax_id": "11144477735", "birth_date": "2009-11-30" }, "linked_subjects": [ { "role": "representative", "subject": { "relationship": "mother" } } ]}La URL del representante se devuelve en la respuesta (linked_subjects[0].url). El integrador la entrega por su propio canal. No se envía ninguna notificación (notify.linked tomó su valor por defecto false). El campo relationship es obligatorio en kyc_minor incluso en la forma shell; consulte Cardinalidad de kyc_minor.
Modo embed (renderizado en iframe)
Sección titulada «Modo embed (renderizado en iframe)»{ "flow_type": "kyc", "surface_hint": "embed", "primary": { "full_name": "Lucas Almeida", "tax_id": "12345678909" }}La bandera surface_hint de la sesión determina la sanitización de PII en GET /api/v1/link/{token} cuando el iframe resuelve el token.
Respuesta de éxito (201)
Sección titulada «Respuesta de éxito (201)»{ "error": false, "data": { "execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "customer_id": "155384e8-c86b-478e-be99-3937d2fee1ab", "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/abc123def456", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/abc123def456", "token": "<token>", "expires_at": "2026-05-13T12:49:34Z", "status": "created", "reference_id": "SOL-2026-00042", "linked_subjects": [ { "role": "representative", "execution_id": "c0aef12b-7c2a-4b3e-9a8d-f1e2d3c4b5a6", "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/def456ghi789", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/def456ghi789", "subject_status": "created", "notify_sent": false } ] }}| Campo | Descripción |
|---|---|
execution_id | Identificador único de la ejecución del sujeto primario. |
customer_id | Identificador del registro del cliente. Ausente en la respuesta en cold-start (no se enviaron datos de primary), porque todavía no existe registro de cliente; presente y estable una vez que el registro existe. Compruebe la presencia de la clave, no el UUID cero: versiones anteriores publicaban aquí 00000000-0000-0000-0000-000000000000 y los integradores lo leían como un identificador que se completaría más tarde, razón por la cual ahora el campo se omite. |
url | URL pública del recorrido del sujeto primario. |
embed_url | URL que apunta a la ruta apta para iframe del sujeto primario. Úsela como src del iframe al renderizar dentro de la aplicación del integrador. |
token | Slug del enlace, aceptado también por GET /api/v1/link/{token}. |
expires_at | Expiración del enlace en ISO 8601. El momento de la creación más un ajuste válido para todo el despliegue, 7 días por defecto. No es configurable por flujo, así que todos los enlaces que emite una instancia llevan la misma ventana. |
status | Estado inicial de la ejecución (created). |
reference_id | Eco del reference_id enviado en la solicitud, cuando se proporciona. |
linked_subjects[] | Una entrada por cada sujeto vinculado creado. Siempre presente (arreglo vacío cuando la solicitud no declaró sujetos vinculados). |
linked_subjects[i].role | Rol del sujeto vinculado (representative en kyc_minor, legal_representative en kyb). |
linked_subjects[i].execution_id | ID de ejecución del sujeto vinculado. |
linked_subjects[i].url | URL del recorrido del sujeto vinculado. |
linked_subjects[i].embed_url | URL apta para iframe del sujeto vinculado. |
linked_subjects[i].subject_status | Estado inicial del sujeto vinculado (created). |
linked_subjects[i].notify_sent | Siempre false en esta respuesta. El envío al representante se difiere hasta que el menor termina, así que el resultado real se lee después en representative_notify_sent de GET /api/v1/link/{token} (consulte Momento del envío). |
El campo url está pensado para el acceso directo del usuario por SMS, WhatsApp, email o cualquier otro canal que elija el integrador. El campo embed_url está pensado para el uso en iframe y en webview. Ambos apuntan a la misma ejecución y resuelven al mismo contenido.
Errores
Sección titulada «Errores»Nota: los campos
messagede error se localizan segúnAccept-Language. Haga la ramificación programática sobrecode, no sobre el texto del mensaje.
| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_REQUEST | JSON malformado, campo desconocido (el mensaje nombra el campo) o tipo de campo incorrecto. |
| 400 | INVALID_IDEMPOTENCY_KEY | El encabezado Idempotency-Key tiene más de 255 caracteres. |
| 400 | VALIDATION_FAILED | Falló la validación a nivel de campo: no se envió ni flow_id ni flow_type, flow_id no es un UUID, flow_id y flow_type no concuerdan, el flujo resuelto está inactivo, channel fuera del enum, relationship o notification_channel fuera del enum, birth_date de kyc_minor fuera del rango de 14 a 17, linked_subjects[i].role inválido, u operation.direction / operation.wallet_type fuera de la lista. El envoltorio no indica cuál de ellas se disparó; correlacione con el request_id. |
| 400 | INVALID_TAX_ID | primary.tax_id no coincide con la forma que emite el país del flujo resuelto. Consulte Tax ID según el país del flujo. |
| 400 | FLOW_ID_REQUIRED | La solicitud envió flow_type sin flow_id, y la instancia tiene más de un flujo activo de ese tipo. Envíe el flow_id leído en GET /flows. |
| 400 | LINKED_SUBJECTS_REQUIRED | Solicitud kyc_minor con el arreglo linked_subjects ausente, vacío o sobredimensionado, o con un rol distinto de representative. |
| 400 | INVALID_FLOW_TYPE | flow_type no es uno de kyc, kyb, kyc_minor, o flow_id se resuelve a un tipo de flujo de uso interno (como kyc_representative). |
| 400 | MISSING_REQUIRED_FIELDS | Solicitud kyc_minor sin primary.tax_id o sin primary.full_name, o bloque operation enviado sin direction o sin wallet_type. |
| 422 | LINKED_SUBJECT_RELATIONSHIP_REQUIRED | Solicitud kyc_minor cuyo linked_subjects[0].subject.relationship está ausente o vacío. Vale para todas las formas de kyc_minor (shell, mixta, materializada). |
| 401 | MISSING_TOKEN | No se envió ni X-API-Key ni Authorization. |
| 401 | INVALID_API_KEY | API Key inválida, revocada o expirada. |
| 401 | INVALID_TOKEN | Solo para quienes llaman con JWT: la credencial bearer no lleva el prefijo gl_live_/gl_test_, así que se verifica como un JWT, y esa verificación falló. |
| 403 | FORBIDDEN | Con JWT: el token no lleva ningún rol autorizado, o la cuenta está deshabilitada. Con API key: la clave declara alcances y ninguno es onboarding:write (consulte Alcances de la clave). |
| 404 | FLOW_NOT_FOUND | El flow_id no existe. |
| 409 | ACTIVE_SESSION_EXISTS | Ya existe una sesión activa para el mismo sujeto primario y el mismo flujo. El cuerpo lleva además execution_id y resume_url de la sesión que bloqueó esta, para que quien llama pueda devolver a la persona a esa sesión en lugar de crear otra. resume_url está presente solo mientras el enlace de esa sesión sigue vigente. |
| 409 | CUSTOMER_ALREADY_REGISTERED | El flujo declara la política de bloqueo y el documento enviado pertenece a un legajo cuya alta ya terminó. No es ACTIVE_SESSION_EXISTS y no lleva resume_url: no hay recorrido en curso para retomar. Ver Política de cliente ya registrado. |
| 422 | PHONE_POLICY_VIOLATION | Un flujo mobile-only recibió un número de línea fija en primary.phone o en linked_subjects[i].subject.phone. |
| 422 | CPF_MINOR_REPRESENTATIVE_MATCH | Solicitud kyc_minor en la que el tax_id del representante es igual al tax_id del menor. |
| 422 | REPRESENTATIVE_AGE_BELOW_MINIMUM | El birth_date del representante indica una edad menor de 18 años. |
| 422 | LINKED_SUBJECT_RELATIONSHIP_REQUIRED | Solicitud kyc_minor cuya entrada de representante omite subject.relationship. |
| 422 | CUIT_NOT_JURIDICAL_PERSON | company.tax_id no es un CUIT de persona jurídica (prefijo 30, 33 o 34). |
| 422 | UNPROCESSABLE | redirect_url no es una URL https absoluta con host, o supera los 2048 caracteres. |
| 500 | INTERNAL_ERROR | Entre otras causas: flow_type nombra un tipo sin ningún flujo activo para el integrador. La búsqueda devuelve un error crudo de “sin filas” que nada mapea a un 4xx, así que una solicitud que en realidad es un error del cliente se manifiesta como un error de servidor. Reintentar no ayuda; corrija el flow_type o envíe flow_id. |
Resolución del enlace
Sección titulada «Resolución del enlace»GET /api/v1/link/{token}Endpoint público (sin autenticación). El frontend del integrador, el iframe o cualquier navegador que abra la url de la respuesta de la sesión llama a este endpoint para cargar el recorrido. Devuelve la definición del flujo, los pasos y el bloque del cliente.
Cuando la sesión se creó con surface_hint: "embed", la respuesta se sanitiza (consulte Comportamiento de la sanitización de PII).
Límite de solicitudes: el tope se comparte por todo el espacio de nombres /api/v1/link/* (no por token) y lo modula el perfil de entorno con el que corre la API. El perfil por defecto permite 10 solicitudes por hora y por IP de origen; el perfil de desarrollo permite 120 solicitudes por minuto. El sandbox se sirve desde un host de desarrollo, así que el sandbox obtiene 120 por minuto y producción obtiene 10 por hora. Los integradores que resuelven varios enlaces en secuencia desde la misma IP de oficina consumen el mismo cupo.
Resolver un token no lo consume ni lo rota. El mismo token se puede volver a resolver hasta que se dispare un reloj (la expiración del enlace o la ventana de sesión activa que se describe en Errores, más abajo).
Respuesta (por defecto)
Sección titulada «Respuesta (por defecto)»{ "error": false, "data": { "execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "flow": { "id": "61000000-0000-0000-0000-000000000010", "type": "kyc_minor", "name": "KYC Menor", "layout_mode": "stepper", "cpf_match_mode": "block", "phone_policy": "mobile-only", "country_code": "BR", "language_code": "pt-BR", "settings": {}, "steps": [ { "id": "...", "execution_step_id": "...", "step_type_code": "welcome", "step_type_name": "Boas-vindas", "step_order": 1, "title": "Boas-vindas", "config": {}, "is_required": true, "is_enabled": true, "status": "pending" } ] }, "customer": { "id": "155384e8-c86b-478e-be99-3937d2fee1ab", "name": "João Santos", "cpf": "12345678909", "cpf_masked": "***8909", "email": "joao@exemplo.com", "phone": "+5511999887766", "birth_date": "2010-05-15" }, "minor": null, "relationship": "mother", "status": "in_progress", "expires_at": "2026-05-13T12:49:34Z", "allowed_origins": ["https://cliente.com"], "representative_link": "https://acme.onp.prod.guardline.com.br/onboarding/link/def456ghi789", "representative_token": "<linked-token>", "representative_status": "created", "representative_notify_sent": true }}| Campo | Descripción |
|---|---|
execution_id | Identificador de ejecución del sujeto al que pertenece este token. |
flow | Definición del flujo: id, type, name, layout_mode, cpf_match_mode (alert o block), phone_policy (any o mobile-only), country_code (ISO 3166-1 alfa-2, en mayúsculas), language_code (la etiqueta canónica, como pt-BR o es-AR), onboarding_channel (solo cuando la ejecución o el flujo declara un canal de atención, como asistido_agencia; nunca web, sdk ni api), settings y los steps ordenados. country_code y language_code viajan siempre: lea el país como dato en lugar de inferirlo del sufijo de un código de tipo de paso. Cada paso lleva id, execution_step_id, step_type_code, step_type_name, step_order, title, description, config, is_required, is_enabled, status y (solo en la superficie por defecto) display_data y completed_fields. |
customer | Registro del cliente. Los campos se detallan más abajo. |
customer.id | UUID del registro del cliente. En cold-start, y en toda ejecución KYB, este es el UUID cero 00000000-0000-0000-0000-000000000000 con cpf_masked vacío. Observe la asimetría con POST /sessions, que omite customer_id en la misma situación: este payload siempre lleva el bloque customer, así que la ausencia de registro se manifiesta como ceros y no como una clave faltante. Aquí compruebe el UUID cero y allí la presencia de la clave. |
customer.name | Nombre completo. Vacío en una fila cold-start, con una excepción: en un representante KYB el resolvedor recae en el nombre sembrado en el momento de la declaración, así que name viene relleno mientras id sigue siendo el UUID cero. |
customer.cpf | CPF sin máscara. Se elimina cuando surface_hint=embed. |
customer.cpf_masked | CPF enmascarado: tres asteriscos seguidos de los últimos cuatro dígitos (***8909). La clave siempre está presente, nunca se omite, y no depende de cpf: lleva la cadena vacía cuando no hay registro de cliente que enmascarar, que es el caso en cold-start y en toda ejecución KYB, y sobrevive a surface_hint=embed aunque el propio cpf sí se elimine allí. |
customer.email | Email. Se elimina cuando surface_hint=embed. |
customer.phone | Teléfono en E.164. Se elimina cuando surface_hint=embed. |
customer.birth_date | Fecha de nacimiento en YYYY-MM-DD. Se elimina cuando surface_hint=embed. |
minor | Cuando este token pertenece al representante de un par kyc_minor, lleva el bloque del cliente del menor (con la misma forma que customer); null en caso contrario. Se pone en null cuando surface_hint=embed. La clave nunca se elimina, así que por el cable viaja "minor": null en lugar de omitirse. |
relationship | Relación del representante con el menor (mother, father, etc.). Presente solo en los tokens kyc_minor; se omite en los demás casos. |
status | Estado actual de la ejecución. Consulte Ciclo de vida de la ejecución. |
expires_at | Expiración del enlace. |
allowed_origins | Orígenes autorizados a embeber el iframe (CSP frame-ancestors). |
representative_link | URL del recorrido de un hijo vinculado. Presente siempre que esta ejecución no tenga padre y tenga al menos un hijo, sin comprobación alguna del tipo de flujo: el resolvedor toma el hijo creado más recientemente de la ejecución, sea cual sea su rol. En un padre KYB ese hijo es uno de los representantes declarados. Se elimina cuando surface_hint=embed. |
representative_token | El token de enlace crudo de ese mismo hijo (el FE lo usa para la redirección). Misma condición de presencia que representative_link. Se elimina cuando surface_hint=embed. |
representative_status | Estado actual de la ejecución de ese hijo, copiado literalmente del código de estado del hijo, así que lleva un estado de ejecución de Ciclo de vida de la ejecución y nada más. Un representante recién provisionado se lee created, no pending: pending no está en el vocabulario de estados y este campo nunca puede llevarlo. Se conserva bajo surface_hint=embed para que el FE sepa si debe hacer polling. |
representative_notify_sent | true cuando el envío al representante (disparado al completar el menor, consulte Momento del envío) devolvió un ID de mensaje del proveedor; false cuando notify.linked estaba desactivado o no había ningún canal alcanzable; ausente cuando la ejecución tiene padre, o no tiene ningún hijo. Se elimina cuando surface_hint=embed. |
redirect_url | Eco del redirect_url con el que se creó la sesión, para que el recorrido sepa adónde enviar a la persona después de la pantalla final. Ausente cuando la sesión no declaró ninguno, en cuyo caso la pantalla final no ofrece salida. Se revalida en la lectura: un valor almacenado que ya no cumple la política de https absoluta se devuelve como ausente en lugar de respetarse. |
company | Empresa asociada a esta ejecución, para que un recorrido KYB pueda abrirse con el CUIT que envió el integrador en lugar de volver a pedirlo. Lleva id, name (razón social, puede estar vacía) y tax_id (CUIT, solo dígitos, devuelto sin máscara). Ausente en los recorridos de personas y en una ejecución KYB cuya empresa todavía no está materializada. Se conserva cuando surface_hint=embed, ver más abajo. |
beneficial_owners_snapshot | La declaración de beneficiarios finales de la empresa (owners, DDJJ, razón social), sembrada en un hijo representante en el momento de la declaración para que el KYC del representante pueda renderizarla y confirmarla. Ausente en los recorridos que no son de representante. En flujos cuyo paso de beneficiarios finales admite la declaración por control, cada entrada de beneficial_owners puede traer ownership_basis (ownership o control) y, cuando es control, control_description (hasta 500 caracteres), en cuyo caso ownership_percentage puede faltar. Lea la ausencia de ownership_basis como ownership. |
Advertencia: en un padre KYB, los campos representative_* exponen a un representante. Los cuatro dependen de una sola condición, que la ejecución no tenga padre, y la consulta que hay detrás toma el hijo creado más recientemente sin filtrar por rol ni por tipo de flujo. Así que resolver el enlace público de un padre KYB devuelve el representative_link de un representante declarado y su representative_token crudo, que es una credencial funcional para el recorrido de esa persona. Cuando una empresa declara varios representantes, el payload nombra exactamente a uno de ellos, el más nuevo, y cuál sea puede cambiar a medida que se agregan representantes. Trate el token de enlace de un padre KYB como algo que también alcanza el recorrido del representante, y use GET .../representatives cuando quiera el conjunto completo en lugar de un miembro arbitrario.
Comportamiento de la sanitización de PII
Sección titulada «Comportamiento de la sanitización de PII»Cuando la sesión se creó con surface_hint: "embed", el resolvedor aplica una sanitización en proceso antes de devolver el payload:
- Se eliminan:
customer.cpf,customer.email,customer.phone,customer.birth_date,minor(se pone ennull),representative_link,representative_token,representative_notify_sent, y todos losflow.steps[].display_datayflow.steps[].completed_fields. - Se conservan:
customer.id,customer.name,customer.cpf_masked, todo el bloqueflow(id, type, name, layout_mode, cpf_match_mode, phone_policy, settings, steps),relationship,status,expires_at,allowed_origins,representative_status,execution_id,company,redirect_url,beneficial_owners_snapshot.
company se conserva de forma deliberada, y la asimetría con customer.cpf es solo aparente. Lo que la regla elimina es el documento de identidad de una persona física, que es lo que lleva customer.cpf con independencia del nombre que le dé el recorrido local. Un CUIT es el identificador fiscal de una empresa y es público en el padrón de ARCA que ese mismo recorrido consulta por CUIT, así que ocultarlo no aportaría privacidad y rompería el prellenado para el que existe. Además, ambos no pueden coexistir: el sujeto de una ejecución es o una persona o una empresa, nunca las dos, así que en un recorrido KYB el bloque del cliente está vacío de todos modos.
Las sesiones creadas sin surface_hint (por defecto) reciben el payload completo.
Errores
Sección titulada «Errores»Nota: los campos
messagede error se localizan segúnAccept-Language. Consulte Idioma de los mensajes de error.
| HTTP | Código | Causa |
|---|---|---|
| 404 | LINK_NOT_FOUND | El token no existe. |
| 410 | LINK_EXPIRED | Dos disparadores, y el segundo se activa mientras expires_at todavía está en el futuro. Uno: expires_at ya pasó. Dos: la ejecución está en in_progress y su ventana de sesión activa, contada desde la primera resolución del enlace, se agotó. La ventana de sesión se aplica únicamente sobre in_progress, así que una ejecución estacionada en created o en pending_representative (un padre KYB a la espera de sus representantes, por ejemplo) nunca es rechazada por ella y se puede seguir resolviendo hasta expires_at. |
| 429 | RATE_LIMITED | Se superó el tope del perfil de entorno, compartido entre todos los tokens: 10 por hora y por IP de origen en el perfil por defecto, 120 por minuto en el perfil de desarrollo con el que corre el sandbox. |
Reenvío del enlace del representante
Sección titulada «Reenvío del enlace del representante»POST /api/v1/onboarding/executions/{execution_id}/resend-representative-linkVuelve a disparar el envío de la notificación para el representante de una ejecución kyc_minor existente. No crea una nueva ejecución de representante; reutiliza el hijo vinculado existente y reintenta la notificación.
Autenticación
Sección titulada «Autenticación»Este endpoint requiere un JWT de admin o de supervisor obtenido desde el backoffice de Guardline. El encabezado X-API-Key por sí solo no basta. Las llamadas sin un JWT válido devuelven 401 MISSING_TOKEN.
El endpoint está pensado para flujos administrativos de recuperación (el equipo de soporte del integrador detecta que el representante no recibió el enlace y el operador pulsa “reenviar” en el backoffice). No está diseñado para llamarse desde la automatización del integrador. Para un envío iniciado por el integrador, ponga notify.linked: true en el POST /sessions original y (si el enlace expira) cree una sesión nueva.
Idempotencia
Sección titulada «Idempotencia»El endpoint respeta el encabezado Idempotency-Key; consulte Idempotencia.
Un máximo de 3 reenvíos por ejecución. El cuarto intento devuelve 429 RATE_LIMITED.
El tope es un contador vitalicio sobre la ejecución, no una tasa dentro de una ventana. No se reinicia: una vez registrados 3 reenvíos, esa ejecución no se puede volver a reenviar nunca, y esperar no ayuda. Lea el 429 como “esta ejecución se quedó sin reenvíos”, no como “intente más tarde”.
Un reenvío fallido igual consume uno de los tres. El contador se incrementa antes de resolver al hijo y antes de intentar el envío, de forma deliberada, para que un integrador no pueda superar el tope provocando errores. Una llamada que responde 404 NO_RESENDABLE_CHILD o que falla durante el envío ya quemó un cupo, así que un operador que se tope con esos casos puede terminar con menos de tres reenvíos utilizables.
Errores
Sección titulada «Errores»| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_ID | El execution_id de la ruta no es un UUID válido. |
| 400 | VALIDATION_FAILED | La llamada es sobre una ejecución del tipo equivocado: el flujo no es kyc_minor (una ejecución KYB o KYC simple cae aquí, no en un 404), o la ejecución no tiene ningún representante vinculado. |
| 401 | MISSING_TOKEN | No se proporcionó el JWT. |
| 403 | FORBIDDEN | El JWT no tiene el rol admin ni supervisor. |
| 404 | EXECUTION_NOT_FOUND | La ejecución primaria no existe. |
| 404 | NO_RESENDABLE_CHILD | No hay un hijo representante en estado no terminal para reenviar. El cupo de reenvío ya está gastado cuando esto ocurre. |
| 409 | EXECUTION_TERMINATED | La ejecución primaria está en approved, rejected, cancelled o expired. Aquí no se comprueba blocked. |
| 429 | RATE_LIMITED | Se alcanzó el tope vitalicio de 3 reenvíos. No se reinicia. |
| 502 | INTERNAL_ERROR | Cualquier otro fallo: leer la ejecución o el flujo, leer o incrementar el contador, o el propio envío de la notificación. |
La comprobación de estado terminal detrás del 409 es más acotada que la tabla del ciclo de vida. El reenvío rechaza únicamente approved, rejected, cancelled y expired; no trata blocked como terminal, aunque Ciclo de vida de la ejecución marque blocked como terminal. Por eso un reenvío sobre una ejecución blocked pasa la validación del 409 y continúa.
Representantes legales KYB
Sección titulada «Representantes legales KYB»Un recorrido KYB no sabe de antemano cuántas personas pueden obligar legalmente a la empresa. La cantidad y la identidad se declaran a mitad del recorrido, en el paso de configuración de representantes, una vez establecido el sujeto empresa. Cada representante declarado se convierte en su propio subflujo de KYC con su propio enlace, de modo que el padre KYB y cada representante avanzan de forma independiente.
Esto generaliza el patrón de representante único de kyc_minor a 1..N, para cuando los representantes no se conocen al crear la sesión; si se conocen, pueden declararse en linked_subjects[] con role: "legal_representative".
Declaración de representantes
Sección titulada «Declaración de representantes»POST /api/v1/onboarding/executions/{execution_id}/representatives{execution_id} es la ejecución padre KYB. Respeta Idempotency-Key.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
representatives | array (de 1 a 20) | Sí | Una entrada por representante legal. Cada entrada usa el mismo bloque de sujeto que linked_subjects[i].subject: full_name, tax_id, email, phone, birth_date, relationship, notification_channel. |
notify | object | No | Banderas de adhesión a las notificaciones, con la misma semántica que en POST /sessions. |
{ "representatives": [ { "full_name": "María Gómez", "tax_id": "27123456780", "phone": "+543794123456" }, { "full_name": "Juan Pérez", "tax_id": "20123456786", "email": "juan@example.com" } ], "notify": { "linked": true }}Respuesta (200):
{ "error": false, "data": { "representatives": [ { "role": "legal_representative", "execution_id": "c0aef12b-7c2a-4b3e-9a8d-f1e2d3c4b5a6", "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/def456ghi789", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/def456ghi789", "link_token": "<link_token>", "expires_at": "2026-05-20T12:49:34Z", "notify_sent": true } ] }}Cada entrada lleva la ejecución hija propia del representante y un enlace nuevo. url retoma el subflujo de KYC de ese representante y es lo que el integrador comparte cuando se encarga él mismo de la entrega; notify_sent informa si Guardline envió el enlace. El envío se intenta cuando notify.linked es verdadero y la entrada lleva o bien un phone o bien un email, no un phone en concreto, razón por la cual la entrada de Juan Pérez de más arriba (email, sin teléfono) sí recibe el envío.
Consulta del progreso de los representantes
Sección titulada «Consulta del progreso de los representantes»GET /api/v1/onboarding/executions/{execution_id}/representativesDevuelve los hijos declarados con su estado en vivo, para que el integrador (o el recorrido) pueda mostrar el progreso de cada representante sin abrir cada enlace.
{ "error": false, "data": { "representatives": [ { "execution_id": "c0aef12b-7c2a-4b3e-9a8d-f1e2d3c4b5a6", "status_code": "in_progress", "full_name": "María Gómez", "link_token": "<link_token>", "expires_at": "2026-05-20T12:49:34Z", "notify_sent": true } ] }}Cada entrada lleva además decision_code y risk_score, que son el veredicto del propio representante. Un representante legal de KYB pasó a puntuarse contra la matriz de persona humana y cierra con veredicto propio, así que el consumidor deja de tener que leer completed como si significara aprobado. Los dos campos se omiten mientras el representante está en curso, que es el caso del ejemplo de arriba, y durante toda la vida del representante legal de un kyc_minor, cuyo desenlace sigue siendo el del menor.
decision_code es el código de decisión crudo, y no el result normalizado que publica la consulta de la ejecución: los valores son auto_approved, manual_approved, auto_rejected, manual_rejected y pending_review. Un consumidor que ramifica solo entre aprobado y rechazado trata mal al representante que cayó en análisis manual.
Un representante que no sale aprobado no rechaza a la empresa: deriva el caso del padre a análisis manual y se nombra ahí, y la decisión de la empresa sigue siendo del analista.
La consulta es “todos los hijos de esta ejecución”, no “todos los representantes legales KYB de esta empresa”. No hay filtro por rol ni por tipo de flujo, así que apuntado a un padre kyc_minor este endpoint devuelve al representante de ese menor, y apuntado a una ejecución sin hijos devuelve un arreglo vacío.
full_name es una cadena vacía en cualquier representante declarado sin tax_id y full_name a la vez. El nombre se lee del registro de persona, y un registro de persona solo se crea cuando la entrada suministra ambos campos, así que una entrada shell (o una que lleva nombre pero no tax id) se consulta de vuelta sin nombre.
Autenticación y límites
Sección titulada «Autenticación y límites»Ambos endpoints aceptan una clave de API o un JWT, y ambos también responden sin ninguna de las dos: el recorrido KYB los abre desde un enlace mágico que no lleva credencial. Ambos están limitados a 60 solicitudes por minuto y por IP de origen, dimensionado para el polling de progreso (unas 6 por minuto y por recorrido).
GET .../representatives está en la práctica sin autenticación, y no hay nada más que el límite de solicitudes delante de él. El handler interpreta el execution_id de la ruta como un UUID y consulta; no hace ninguna comprobación de existencia, ni de propiedad, ni de tipo de flujo. Por eso cualquier UUID bien formado devuelve 200 con un arreglo representatives, vacío cuando el id no corresponde a nada, y nunca se le pregunta a quien llama quién es. Cada fila que sí devuelve lleva el link_token de ese representante, que es una credencial funcional para el recorrido de esa persona.
Trate el execution_id como un secreto por esa razón: conocerlo basta para leer los representantes de una empresa y para obtener sus tokens de enlace vigentes. No lo ponga en una URL que pegaría en un ticket, en una línea de log que envía a un tercero, ni en una cadena de consulta que un encabezado de referente pueda filtrar.
Errores
Sección titulada «Errores»| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_ID | execution_id no es un UUID válido. En ambos verbos. |
| 400 | INVALID_REQUEST | Solo en POST. JSON malformado o tipo de campo incorrecto. |
| 400 | MISSING_REQUIRED_FIELDS | Solo en POST. representatives está ausente o es null. Este es el código que devuelve un arreglo faltante, no VALIDATION_FAILED. |
| 400 | VALIDATION_FAILED | Solo en POST. representatives presente pero vacío, más de 20 entradas o un fallo de validación de campo dentro de una entrada. |
| 400 | VALIDATION_FAILED | Solo en POST. Rechazos estructurales, y los que más golpean a un integrador: execution_id nombra una ejecución hija en lugar del padre; el estado del padre está fuera de created, in_progress y pending_representative; el flujo del padre no es kyb. |
| 404 | EXECUTION_NOT_FOUND | Solo en POST. La ejecución padre no existe. El GET no tiene comprobación de existencia, así que responde 200 con un arreglo vacío ante un id desconocido y nunca puede devolver este código. |
| 429 | RATE_LIMITED | Se superó el límite de 60 por minuto por IP. En ambos verbos. |
| 502 | INTERNAL_ERROR | Error interno al provisionar o listar los hijos. En ambos verbos. |
Un fallo de configuración se esconde dentro del mismo código. Si el flujo KYB del tenant no tiene ningún subflujo de representante enrutado hacia él, ni tampoco un subflujo de respaldo, el aprovisionamiento no puede resolver el flujo en el que crear los hijos y la llamada responde el mismo 400 VALIDATION_FAILED indiferenciado. Nada en el payload lo distingue de una solicitud incorrecta, así que un VALIDATION_FAILED que sobrevive a un cuerpo por lo demás correcto es un problema de configuración del tenant que hay que plantear a Guardline, no una solicitud que corregir.
Dispensador de QR reutilizable
Sección titulada «Dispensador de QR reutilizable»Un dispensador es un token estable detrás de un código QR que emite una sesión de onboarding nueva en cada lectura. Es la respuesta al caso de uso de sucursales y eventos, donde un único código impreso atiende a muchas personas distintas, y existe porque un enlace de sesión normal es de sujeto único: reutilizarlo metería a una segunda persona dentro de la ejecución de la primera.
La elegibilidad es una lista de exclusión, no una lista de inclusión. Se rechazan exactamente dos tipos de flujo, kyc_minor y kyc_representative, porque esos recorridos se provisionan como un par declarado en lugar de abrirse a quien se acerque. Todos los demás tipos se aceptan, lo que cubre kyc y kyb, los dos para los que existe esta función, y también kyb_representative, para el que la API emite un dispensador sin protestar aunque Recorridos disponibles advierta que nunca hay que iniciar una sesión contra él. Verifique el flow_id que envía.
Creación o consulta del dispensador
Sección titulada «Creación o consulta del dispensador»POST /api/v1/onboarding/dispensersRequiere una clave de API con onboarding:write (cuando la clave declara alcances) o un JWT de backoffice con rol admin, supervisor o analyst. Idempotente por diseño: un dispensador activo por flujo, así que llamarlo de nuevo devuelve el existente en lugar de emitir un segundo. Dos llamadas simultáneas sobre un flujo sin enlace activo reciben el mismo enlace.
{ "flow_id": "61000000-0000-0000-0000-000000000090" }Respuesta (200):
{ "data": { "dispenser_token": "<dispenser_token>", "flow_id": "61000000-0000-0000-0000-000000000090", "url": "https://acme.onp.prod.guardline.com.br/onboarding/d/kQ8xR2mN7pL4" }}url es el valor que se debe renderizar como código QR. Es un destino de lectura, no un enlace de recorrido.
Observe el envoltorio: esta ruta, la ruta de dispensado y las rutas de ciclo de vida de más abajo no usan el envoltorio de éxito estándar descrito en Formato de respuesta. En el cuerpo no hay clave error, solo data.
“Obtener” significa el enlace activo del flujo, aun cuando un tope (vea Límites de uso) ya lo detuvo por vencimiento o por cantidad de sesiones: esta ruta no crea un segundo enlace mientras haya uno activo. Para un enlace nuevo, use Rotación; para quitar el tope, use Límites de uso. Cuando la llamada se autentica con un JWT de backoffice y el enlace se crea en ella, el usuario queda registrado en created_by; una API key no lleva usuario, así que created_by queda null.
| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_REQUEST | Cuerpo JSON malformado o tipo de campo incorrecto. |
| 400 | VALIDATION_FAILED | flow_id ausente o no es un UUID. |
| 401 | MISSING_TOKEN / INVALID_API_KEY | No se envió ninguna credencial, o la clave de API es desconocida, está revocada o expiró. |
| 404 | FLOW_NOT_FOUND | El flujo no existe, o existe pero está inactivo. Ambos casos responden el mismo código. |
| 422 | INVALID_FLOW_TYPE | El tipo de flujo no admite un dispensador reutilizable (kyc_minor, kyc_representative). |
| 403 | FORBIDDEN | JWT sin rol admin, supervisor o analyst, o API key cuyos alcances no incluyen onboarding:write. |
| 409 | ALREADY_EXISTS | Caso residual de dos creaciones simultáneas en que el enlace ganador se desactivó antes de devolverse. Repita la llamada. |
Dispensado de una sesión
Sección titulada «Dispensado de una sesión»POST /api/v1/onboarding/dispensers/{token}/sessionsPúblico, llamado una vez por lectura por el propio navegador de la persona. La posesión del token del dispensador es la capacidad, así que no se envía ninguna clave de API ni ningún JWT. Limitado a 30 solicitudes por minuto y por IP de origen.
{ "data": { "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/abc123def456", "token": "<token>", "expires_at": "2026-05-20T12:49:34Z", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/abc123def456" }}Igual que en la ruta de creación de más arriba, en este cuerpo no hay clave error.
| Campo | Descripción |
|---|---|
url | Enlace de un solo uso de la sesión recién creada, para abrir fuera de un iframe. |
token | Token de la sesión recién creada. Es el mismo token que aparece en url y en embed_url. |
expires_at | Vigencia de la sesión creada, no del enlace reutilizable. |
embed_url | URL compatible con iframe de la misma sesión (/embed/t/<token>). Es el destino de un enlace reutilizable abierto dentro de un iframe del integrador. |
Cada llamada crea una nueva sesión cold-start, así que cada lectura produce un enlace distinto y una ejecución distinta. Este endpoint deliberadamente no respeta Idempotency-Key: repetir una respuesta cacheada entregaría el mismo recorrido a dos personas, que es justamente lo que el dispensador existe para evitar.
| HTTP | Código | Causa |
|---|---|---|
| 400 | MISSING_TOKEN | Falta el token del dispensador en la ruta. |
| 404 | LINK_NOT_FOUND | El dispensador no existe, fue desactivado o fue reemplazado por una rotación. |
| 410 | LINK_EXPIRED | El dispensador está activo, pero un tope lo detuvo: expires_at ya pasó o se alcanzó max_sessions. Un enlace sin topes nunca responde 410. |
| 429 | RATE_LIMITED | Se superó el límite de 30 por minuto por IP. |
El conteo de sesiones es atómico: dos lecturas simultáneas no superan max_sessions. Si la creación de la sesión falla después de tomar el cupo, el cupo se devuelve.
Enlace directo y enlace incorporado
Sección titulada «Enlace directo y enlace incorporado»La url devuelta al crear el dispensador apunta a /onboarding/d/<dispenser_token>, la página que abre el navegador al leer el QR. Esa página llama a la ruta de dispensado de más arriba y lleva a la persona a la url de la sesión creada.
Para abrir el enlace reutilizable dentro de un iframe, use el mismo host y el mismo token en la ruta /embed/d/<dispenser_token>. Esa página llama a la misma ruta de dispensado y lleva a la persona a la embed_url de la sesión creada, de modo que el recorrido sigue en el shell incorporado. Agregue ?origin=<origen de su página>, como en cualquier embed_url. El token del dispensador crea sesiones mientras el enlace esté activo: trate las dos URL como credenciales y no las registre en logs.
Qué ve la persona en cada rechazo
Sección titulada «Qué ve la persona en cada rechazo»Las dos páginas muestran una pantalla específica para cada rechazo de la ruta de dispensado:
| HTTP | Pantalla | Puede reintentar en la misma página |
|---|---|---|
404 LINK_NOT_FOUND | ”Link no disponible”: el enlace no existe o la institución lo desactivó. | No. La respuesta es la misma en cada intento. |
410 LINK_EXPIRED | ”Link vencido o sin cupo”: el enlace venció o alcanzó el límite de altas. | No. Hay que ajustar los límites de uso o hacer una rotación. |
429 RATE_LIMITED | ”Demasiados intentos”: demasiadas lecturas desde la misma conexión en poco tiempo. | Sí, después de esperar. |
| Otros (red, 5xx) | “Error temporal”. | Sí. |
Ninguna de estas pantallas muestra un contacto de la institución, porque el rechazo ocurre antes de resolver cualquier flujo.
Ciclo de vida del enlace reutilizable
Sección titulada «Ciclo de vida del enlace reutilizable»Cuatro rutas gestionan los enlaces de un flujo. Todas están en el grupo /api/v1/onboarding y aceptan API key o JWT. Con API key, la lectura exige el alcance onboarding:read y las tres escrituras exigen onboarding:write, cuando la clave declara alcances. Con JWT de backoffice, el inventario exige el rol admin, supervisor o analyst, y las tres escrituras exigen admin o supervisor (403 FORBIDDEN para los demás).
Todas devuelven el enlace con la misma forma:
{ "data": { "id": "7d0c3f7e-2a51-4a8e-9b0e-3f7d41b2c9a1", "dispenser_token": "<dispenser_token>", "flow_id": "61000000-0000-0000-0000-000000000090", "url": "https://acme.onp.prod.guardline.com.br/onboarding/d/kQ8xR2mN7pL4", "status": "active", "is_active": true, "created_by": null, "created_at": "2026-09-16T14:02:11Z", "deactivated_at": null, "deactivated_by": null, "max_sessions": 500, "expires_at": "2026-12-31T23:59:59Z", "sessions_dispensed": 37, "rotated_from": null }}| Campo | Descripción |
|---|---|
id | Identificador del enlace, usado en el path de las escrituras. No es el dispenser_token. |
status | Derivado en el momento de la lectura: inactive cuando is_active es false; si no, expired cuando expires_at ya pasó; si no, exhausted cuando sessions_dispensed alcanzó max_sessions; si no, active. |
is_active | false después de una desactivación o de una rotación. Un enlace expired o exhausted sigue con is_active: true. |
created_by, deactivated_by | Id del usuario de backoffice que creó o desactivó el enlace. null cuando la acción vino de una API key o es anterior a este registro. |
max_sessions, expires_at | Los dos topes opcionales. null significa sin tope. |
sessions_dispensed | Sesiones emitidas por el enlace. El conteo empezó cuando se publicó el ciclo de vida, así que un enlace antiguo no incluye las sesiones que emitió antes. |
rotated_from | Id del enlace que este reemplazó, cuando nació de una rotación. |
Inventario
Sección titulada «Inventario»GET /api/v1/onboarding/dispensers?flow_id={flow_id}Todos los enlaces del flujo, activos o no, del más nuevo al más antiguo, como arreglo en data. El flujo tiene que existir, pero no hace falta que esté activo, para que los enlaces de una versión retirada sigan siendo auditables.
Desactivación
Sección titulada «Desactivación»POST /api/v1/onboarding/dispensers/{id}/deactivateSin cuerpo. Apaga el enlace y registra quién lo apagó. Desde ese momento, una lectura de la URL o del QR responde 404 LINK_NOT_FOUND, como un token desconocido. Idempotente: desactivar un enlace ya apagado responde 200 con el enlace tal como está, conservando el registro de la primera desactivación.
Rotación
Sección titulada «Rotación»POST /api/v1/onboarding/dispensers/{id}/rotateSin cuerpo. En una única transacción, apaga el enlace y crea su sucesor para el mismo flujo, con token y URL nuevos, el mismo layout, los mismos topes y el conteo de sesiones en cero. Responde 200 con el enlace nuevo, cuyo rotated_from apunta al anterior. Es la respuesta a un enlace filtrado: la URL y el QR anteriores dejan de funcionar en el acto, y el QR impreso debe reemplazarse por la nueva url.
max_sessions se copia tal como está, con el conteo de sesiones en cero. expires_at solo se copia mientras sigue en el futuro: rotar un enlace cuyo expires_at ya pasó crea un sucesor sin vencimiento (expires_at: null), que funciona enseguida en lugar de nacer expired y responder 410. Para darle un nuevo vencimiento al sucesor, use Límites de uso a continuación.
Límites de uso
Sección titulada «Límites de uso»PUT /api/v1/onboarding/dispensers/{id}/limits{ "max_sessions": 500, "expires_at": "2026-12-31T23:59:59Z" }Reemplaza los dos topes de un enlace activo de una vez. Un campo ausente o null quita ese tope, así que {} vuelve ilimitado al enlace, y enviar solo max_sessions borra un expires_at existente. max_sessions va de 1 a 1.000.000 y se compara con sessions_dispensed, que no se reinicia: un tope menor o igual a lo que el enlace ya emitió lo deja exhausted de inmediato. expires_at es un instante RFC 3339 y tiene que estar en el futuro.
Errores del ciclo de vida
Sección titulada «Errores del ciclo de vida»| HTTP | Código | Causa |
|---|---|---|
| 400 | VALIDATION_FAILED | flow_id o id no es un UUID, max_sessions fuera de 1 a 1.000.000, o un expires_at que no está en el futuro. |
| 400 | INVALID_REQUEST | Cuerpo JSON mal formado en .../limits, incluido un expires_at fuera del formato RFC 3339. |
| 401 | MISSING_TOKEN / INVALID_API_KEY | No se envió credencial, o la API key es desconocida, está revocada o expiró. |
| 403 | FORBIDDEN | JWT sin rol admin o supervisor en una escritura, o API key cuyos alcances no incluyen el exigido. |
| 404 | FLOW_NOT_FOUND | Inventario de un flow_id que no existe. |
| 404 | LINK_NOT_FOUND | No hay enlace con ese id. |
| 409 | CONFLICT | Rotación o cambio de límites de un enlace ya desactivado. La desactivación nunca responde 409. |
Ciclo de vida de la ejecución
Sección titulada «Ciclo de vida de la ejecución»Diagrama de estados
Sección titulada «Diagrama de estados»Conviene enunciar de forma explícita dos reglas que codifican las aristas anteriores. expires_at se aplica únicamente mientras la ejecución está en created o en in_progress: la consulta de expiración no selecciona ningún otro estado y es la única que escribe expired en todo el sistema. Por eso un padre kyc_minor estacionado en pending_representative nunca vence por ese reloj, por mucho que espere; cuando un hijo expira, el worker emite un evento de vinculación sobre el padre y deja intacto el estado del propio padre. Y created termina con el envío del primer paso, no con la apertura del enlace: resolver el enlace marca el inicio de la sesión y nada más, así que un usuario que abre el enlace y lo abandona sigue en created.
Descripción de los estados
Sección titulada «Descripción de los estados»| Estado | Terminal | Descripción |
|---|---|---|
created | No | Sesión creada, todavía no se envió ningún paso. Abrir el enlace no saca de este estado; el envío del primer paso sí. |
in_progress | No | El usuario está navegando por los pasos del recorrido. |
pending_representative | No | A la espera de uno o más representantes vinculados. Se alcanza de dos maneras: un padre kyc_minor cuyo menor terminó su recorrido, y un padre kyb cuyos representantes se declararon (consulte Representantes legales KYB). Un padre KYB queda estacionado desde created o desde in_progress, y la segunda es la vía normal, ya que los representantes se declaran a mitad del recorrido, en el paso de configuración de representantes. No interprete el estado como “el sujeto terminó”. |
processing | No | Todos los pasos enviados; el motor de decisión está procesando. |
pending_review | No | Derivado a revisión manual. No hay ningún plazo de resolución en el contrato de la API: un worker interno genera un escalamiento sobre un caso que lleva aquí más de un umbral configurable, lo que dirige la atención en lugar de cerrar el caso. |
approved | Sí | Aprobado por el motor o por un analista. |
rejected | Sí | Rechazado por el motor o por un analista. |
completed | Sí | Todos los pasos terminados sin ninguna decisión propia de la ejecución, que es como cierra un hijo vinculado cuando el veredicto vive en su padre: el representante de kyc_minor es el caso con el que se topan los integradores. No lleva bloque decision. Consulte la ejecución hija vinculada, como este documento recomienda para kyc_minor, y este es el estado que obtiene de vuelta. |
blocked | Sí | Recorrido interrumpido por una regla de seguridad (restricción de CPF, límite de intentos de biometría). |
cancelled | Sí | Cancelado por el integrador o por un administrador. |
expired | Sí | Sesión expirada antes de completarse. |
Subestado de vinculación para kyc_minor
Sección titulada «Subestado de vinculación para kyc_minor»Para kyc_minor, la ejecución padre mantiene el estado del ciclo de vida anterior, mientras que el progreso del representante se rastrea por separado en la ejecución hija vinculada. GET /api/v1/link/{token} sobre el token del padre expone el estado del representante mediante el campo representative_status.
El padre pasa a pending_representative cuando el menor completa su recorrido. Mientras el representante hace su parte, el padre permanece en pending_representative; la ejecución hija recorre created → in_progress → processing → terminal de forma independiente. Cuando el hijo alcanza un estado terminal positivo, se ejecuta la consolidación del padre y este pasa por processing hasta su propio estado terminal.
Plazos y expiración
Sección titulada «Plazos y expiración»Circula la idea de que el enlace vale 24 horas, o de que vale 7 días. Son tres contadores distintos, cada uno gobernando un estado diferente, y el primero que vence expira la ejecución.
| Reloj | Origen | Predeterminado | Solo expira mientras el estado es |
|---|---|---|---|
link_expires_at | El LINK_EXPIRATION_DAYS de la instalación | 7 días | created |
expires_at | El expires_in_hours del propio flujo | 168 h, es decir 7 días | created o in_progress |
| Sesión | El SESSION_TIMEOUT_HOURS de la instalación, contado desde started_at | 24 h | in_progress |
Qué significa cada uno en la práctica:
link_expires_atcubre el enlace que nunca se abrió. Como solo actúa sobrecreated, abrir el enlace una vez ya saca a la ejecución del alcance de ese reloj.expires_ates el plazo de la ejecución entera, contado desde la creación y derivado del flujo, no de una variable de entorno. Es el único que sigue corriendo después de que la persona empezó a responder.- El contador de sesión arranca cuando la persona abre el enlace por primera vez, y es el que cierra la jornada abandonada a medias. Es el reloj de 24 horas, y no es el plazo del enlace.
Consecuencia para el mensaje que el integrador le muestra a su usuario: quien abandonó la jornada a medias perdió la sesión de 24 horas, no el enlace de 7 días, y son cosas distintas de decir.
Los dos primeros se publican en la consulta de la ejecución, como expires_at y
link_expires_at, así que el integrador no tiene que recalcularlos.
Un pedido de documentación complementaria reinicia los tres: el enlace se
rota, expires_at se vuelve a estampar desde la ventana del flujo y el contador
de sesión vuelve a cero. Sin eso, el enlace reabierto heredaría un plazo ya
vencido y moriría al llegar.
Consulta de una ejecución
Sección titulada «Consulta de una ejecución»GET /api/v1/onboarding/executions/{execution_id}Devuelve el estado completo de una ejecución, incluidos los datos recogidos durante el recorrido. Este endpoint es la fuente de datos principal para el integrador. Los webhooks notifican las transiciones de estado; los detalles se obtienen mediante esta consulta.
Respuesta de KYC estándar (200)
Sección titulada «Respuesta de KYC estándar (200)»{ "error": false, "data": { "execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "flow_type": "kyc", "reference_id": "SOL-2026-00099", "status": "approved", "decision": { "result": "approved", "risk_level": "low", "decided_at": "2026-03-26T15:30:00Z", "decided_by": "engine" }, "customer": { "full_name": "Carlos Oliveira", "tax_id": "11144477735", "tax_id_masked": "***7735", "birth_date": "1990-05-15", "nationality": "Brasileira", "email": "carlos@exemplo.com", "phone": "+5511999990000", "income": "8500.00", "is_pep": false, "has_foreign_tax_obligation": false, "address": { "zip_code": "01310-100", "street": "Avenida Paulista", "number": "1000", "complement": "Sala 302", "neighborhood": "Bela Vista", "city": "São Paulo", "state": "SP" }, "document": { "type": "cnh", "status": "verified" }, "biometric": { "status": "verified" } }, "data_divergences": [], "links": { "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/abc123def456", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/abc123def456" }, "steps_completed": 8, "steps_total": 8, "created_at": "2026-03-26T14:00:00Z", "started_at": "2026-03-26T14:05:00Z", "completed_at": "2026-03-26T14:12:00Z", "expires_at": "2026-04-02T14:00:00Z" }}| Campo | Descripción |
|---|---|
execution_id | Identificador de la ejecución. |
status | Estado actual de la ejecución. Consulte Ciclo de vida de la ejecución. |
awaiting_customer | true mientras el recorrido está de vuelta con el solicitante porque un analista pidió algo, y false en cualquier otro caso. Existe porque in_progress por sí solo no distingue los dos: después de un pedido de documentación la ejecución vuelve a in_progress, el mismo código que lleva mientras la persona recorre el trayecto por primera vez. Se deriva en la lectura y nunca se almacena, y deliberadamente no es un estado nuevo: un código nuevo en status rompería a todo integrador que ya trata el vocabulario actual. Una salvedad: se resuelve solo en la lectura de una ejecución, así que en Listado de ejecuciones siempre viene false. Cubre los dos caminos que devuelven el recorrido al solicitante: el pedido que hace un analista y el pedido automático de documentación que la propia plataforma dispara por el nivel de debida diligencia. |
classification | Devuelve el valor ocasional o habitual que el integrador declaró al crear la sesión. Se lee de vuelta para que quien llamó confirme lo que la plataforma registró, y para que el recorrido y la mesa de análisis lean una sola fuente en lugar de la copia de la aplicación. Ausente cuando la sesión nunca se clasificó. |
flow_type | Tipo de recorrido. |
reference_id | Eco del correlacionador del integrador, cuando la sesión declaró uno. |
parent_execution_id | Nombra al padre en una ejecución hija vinculada (un kyb_representative o un representante de kyc_minor), de modo que el recorrido de un representante se pueda correlacionar con el onboarding que lo originó sin mantener un mapa local. Ausente en las ejecuciones raíz. |
steps_completed / steps_total | Progreso del recorrido. |
decision | Bloque de decisión: result, risk_level, decided_by, decided_at. Ausente hasta que existe una decisión. result se normaliza a partir del código de decisión interno, y solo se mapean los cuatro códigos terminales (auto_approved y manual_approved a approved, auto_rejected y manual_rejected a rejected); cualquier otro código se devuelve crudo. pending_review es en sí mismo un código de decisión, así que una ejecución bajo revisión publica "result": "pending_review". El código que ramifica solo entre approved y rejected trata mal todos los casos en revisión. |
customer | Persona que es el sujeto de la ejecución. Los campos se detallan más abajo. Ausente en las ejecuciones KYB en este endpoint. |
company | Sujeto empresa, en las ejecuciones KYB: legal_name, tax_id (bruto, sin formato) y trade_name, tax_category, company_type y main_activity cuando el recorrido los recogió. main_activity es la actividad principal declarada en texto, no un código de catálogo: no intente resolverla como customer.activity_code. El sujeto de una ejecución es una persona o una empresa, nunca ambos, así que en este endpoint los dos bloques son mutuamente excluyentes: una lectura de detalle KYB omite la clave customer por completo. Esa exclusividad vale solo en el detalle. Una fila de listado KYB emite los dos, porque allí el bloque customer refleja las proyecciones de la empresa por compatibilidad hacia atrás; consulte Listado de ejecuciones. Las filas del listado proyectan solo legal_name y tax_id. |
device | Proyección de riesgo de dispositivo del recorrido. Los campos se detallan más abajo. Solo en la lectura de una ejecución individual: el endpoint de listado lo omite a propósito, ya que rellenarlo cuesta una lectura y un parseo de payload por elemento en cada página. |
data_divergences | Siempre presente como arreglo, vacío cuando no hay ninguna. Cada entrada reporta un campo cuyo valor enviado durante el recorrido difirió del valor prellenado: field, expected_masked, submitted_masked, step_id, recorded_at. Los valores se enmascaran al insertarse; los valores crudos nunca salen del backend. field es uno de name, email, phone, birth_date, cpf e identity_document: los flujos fuera de Brasil registran la divergencia del documento como identity_document, y los flujos brasileños y las filas antiguas siguen usando cpf. Contemple los dos. |
created_at / started_at / completed_at | Marcas de tiempo del recorrido. |
expires_at | El plazo de la ejecución, derivado del expires_in_hours del flujo. |
link_expires_at | La ventana aparte durante la cual el enlace se puede seguir abriendo. Resolver el enlace no consume ni rota el token, así que el mismo enlace sigue funcionando hasta que se dispare este reloj. |
links | El enlace del recorrido de la ejecución, como { url, embed_url }: url es el enlace que se envía a la persona y embed_url su variante para iframe, con la misma forma que url y embed_url de POST /sessions, y siempre refleja el enlace actual de la ejecución. Los integradores pueden confiar en links.url para recuperar de nuevo el enlace del recorrido (por ejemplo, para reenviarlo) en lugar de crear otra sesión. A diferencia de POST /sessions, esta respuesta no trae url, embed_url ni token en el nivel superior: lea el enlace de este bloque. El bloque se omite por completo cuando la ejecución nunca recibió un enlace. Se devuelve sea cual sea el estado del enlace; que el enlace todavía abra depende de link_expires_at y del estado de la ejecución. |
expires_at y link_expires_at se devuelven los dos porque ninguno por sí solo dice cuándo muere el recorrido. El worker de expiración retira una ejecución según el primero de tres relojes que se dispare: link_expires_at mientras sigue en created, expires_at mientras está en created o in_progress, y la ventana de inactividad de la sesión una vez en in_progress. Devolver solo expires_at hacía que los integradores leyeran la cifra de 7 días del enlace en POST /sessions y la cifra menor de la ejecución aquí bajo el mismo nombre, sin manera de saber cuál manda.
Bloque customer
Sección titulada «Bloque customer»| Campo | Descripción |
|---|---|
full_name | Nombre completo tal como se recogió. |
given_name / surname | Las mitades discriminadas de full_name, resueltas a partir de las columnas que escribe el paso de nombre localizado, luego del payload de ese paso y, por último, de una división de full_name. full_name siempre se envía junto a ellas para que un consumidor pueda recomponer el valor exacto cuando un nombre de pila compuesto se divide de una forma distinta a la esperada. |
tax_id | El documento de identidad de la persona, crudo y sin formato. En los recorridos brasileños es el CPF. En los recorridos localizados es lo que el recorrido recoja como documento de identidad: el DNI, la cédula o el pasaporte. |
tax_number | El identificador fiscal de la persona, en los recorridos argentinos: el CUIL, o el CUIT cuando declaró uno. Es un campo deliberadamente separado de tax_id, porque llevar el identificador fiscal a tax_id cambiaría en silencio el significado de un campo que los integradores ya leen como documento de identidad. Lo recoge el paso de documento, así que está vacío en los recorridos brasileños y en cualquier flujo localizado que no lo recoja. Trátelo como opcional. Este es el campo por el que indexar un perfil de usuario en una integración argentina. |
tax_id_masked | Variante enmascarada: tres asteriscos seguidos de los últimos cuatro caracteres del documento de identidad, cualquiera sea el documento, así que 11144477735 se publica como ***7735 y el DNI 12345678 como ***5678. El Listado de ejecuciones lo enmascara igual. En desuso suave: prefiera tax_id. Muéstrelo, nunca haga parsing de él. |
birth_date, nationality, email, phone, income | Tal como se recogieron. phone lleva el prefijo telefónico siempre que la fuente lo tenga, de modo que un número recogido por un recorrido localizado se lee como E.164 y no como dígitos nacionales sin país. |
is_pep, has_foreign_tax_obligation | Siempre se emiten, incluso cuando valen false. |
activity_code, activity_name, occupation | Actividad económica declarada: código del catálogo, su nombre y la ocupación. Se leen del legajo del cliente y, si faltan, de los datos del paso. Se omiten cuando el recorrido no los recogió. |
address | zip_code, street, number, complement, neighborhood, city, state. |
document, biometric | Bloques de estado de la verificación: type y status. |
representative | Datos del representante anidados dentro de customer. Una forma más acotada que el bloque anterior, no la misma: lleva full_name, tax_id_masked, birth_date, nationality, email, phone, income, is_pep, has_foreign_tax_obligation, address, document, biometric, más relationship, guardianship_document y status. Aquí no hay tax_id crudo, ni given_name, ni surname, ni tax_number; el id enmascarado es la única forma del documento que se obtiene. Reservado por compatibilidad hacia atrás; consulte la nota sobre kyc_minor más abajo. |
Bloque device
Sección titulada «Bloque device»Presente cuando el recorrido tiene un perfil de dispositivo legible y la instancia tiene el resolvedor de dispositivo conectado. Las dos ausencias se ven igual en el cable: una instancia que no ejecuta ningún perfilado de dispositivo omite el bloque en todas las ejecuciones, exactamente igual que una ejecución con un perfil ilegible. Deliberadamente más acotado que el perfil que ve el analista: la forensia de IP (dirección, ISP, ciudad, coordenadas, ASN, código postal) y el detalle de la huella del navegador se quedan detrás de las superficies internas autenticadas en lugar de viajar en una llamada saliente. ip_country es el único campo geográfico que se conserva, porque la granularidad de país es la que necesita la lectura antifraude y no ubica a una persona.
| Campo | Descripción |
|---|---|
provider, device_id, advice | provider es siempre el código estable device_profiling, no el nombre del proveedor. device_id es un identificador opaco de Guardline con la forma dev_ seguida de 24 caracteres hexadecimales, estable para el mismo dispositivo entre ejecuciones, y no el identificador del proveedor. advice es la recomendación. |
risk_score, risk_label | Score de riesgo del proveedor y su franja. |
is_rooted, is_emulator, is_vpn, is_proxy, is_tor, is_bot, is_remote_access, is_incognito | Siempre se emiten, incluso cuando valen false, para que un receptor pueda distinguir false de ausente. La ausencia se expresa omitiendo el bloque device entero. |
device_type, os, os_version, ip_country | Metadatos del dispositivo y del origen. |
signals | Nombres de las señales del proveedor detrás de las banderas anteriores (por ejemplo Jailbreak, Root hiding application), para que un receptor que quiera la evidencia de una bandera no tenga que volver a llamar. Solo nombres. |
Respuesta de KYC Minor (200)
Sección titulada «Respuesta de KYC Minor (200)»Para kyc_minor, el bloque customer de la ejecución padre describe al menor. Los datos del representante se obtienen consultando por separado la ejecución hija vinculada. Los datos de representative embebidos dentro de la respuesta del padre están reservados para casos de compatibilidad hacia atrás y pueden quedar obsoletos en una versión futura; las integraciones nuevas deberían consultar la ejecución hija vinculada de forma explícita usando el execution_id que devuelve linked_subjects[].execution_id en POST /sessions.
El campo relationship del registro de cliente del representante acepta: mother, father, grandparent, legal_guardian, tutor, other. Cuando la relación es distinta de mother o father, el recorrido del representante exige subir un documento de tutela; el estado de la carga se refleja en guardianship_document (null o "uploaded").
El bloque decision solo se rellena cuando la ejecución alcanza un estado terminal (approved, rejected) o está bajo revisión (pending_review). En todos los demás estados la clave está ausente de la respuesta, no presente con valor null. Lo mismo vale para todos los bloques opcionales de este payload (customer, company, device, parent_execution_id, address, document, biometric): compruebe la presencia de la clave. La única excepción deliberada es guardianship_document, que se emite como un null explícito cuando no se subió ningún documento, y data_divergences, que siempre está presente como arreglo.
Errores
Sección titulada «Errores»| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_ID | El execution_id de la ruta no es un UUID válido. |
| 401 | MISSING_TOKEN | No se envió ninguna credencial, ni en X-API-Key ni en Authorization: Bearer. |
| 401 | INVALID_API_KEY | API Key inválida, revocada o expirada. |
| 404 | EXECUTION_NOT_FOUND | No existe ninguna ejecución con ese execution_id. |
| 502 | INTERNAL_ERROR | Cualquier otro fallo al leer la ejecución o su flujo. |
El 404 es una comprobación de existencia y nada más. La lectura no lleva ningún predicado de tenant, así que no puede decirle que un id pertenece a otra persona; el aislamiento aquí es la base de datos dedicada por instancia que se describe en Entornos, no una prueba de propiedad a nivel de fila.
Listado de ejecuciones
Sección titulada «Listado de ejecuciones»GET /api/v1/onboarding/executionsDevuelve una lista paginada de ejecuciones. Por defecto quedan fuera de la respuesta las ejecuciones en estado terminal (approved, rejected, blocked, expired, cancelled); pase un status explícito para incluirlas.
El filtro es la exclusión por estado y nada más: no hay ninguna comprobación aparte de si el enlace o la sesión de una fila siguen vigentes, así que una fila cuyo enlace expiró pero cuyo estado todavía no se retiró se sigue devolviendo.
Los resultados se ordenan por created_at descendente, con execution_id descendente como criterio de desempate determinista entre filas creadas en el mismo instante.
Parámetros de consulta
Sección titulada «Parámetros de consulta»| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
page | integer | 1 | Página solicitada. |
per_page | integer | 20 | Elementos por página (máximo: 100). Los valores por encima de 100 se recortan a 100. |
status | string | (ninguno) | Filtra por estado de la ejecución. Cuando se omite, se excluyen los estados terminales (approved, rejected, blocked, expired, cancelled); pasar un estado explícito lo incluye. Los códigos desconocidos devuelven una página vacía. |
reference_id | string (≤ 255) | (ninguno) | Coincidencia exacta con el reference_id del integrador. Al ser una búsqueda por clave exacta, devuelve la fila en cualquier estado: la exclusión de estados terminales por defecto no se aplica, así que esta es la forma de obtener una ejecución cuyo id ya se conoce sin tener que adivinar su estado. |
flow_id | UUID | (ninguno) | Filtra por una versión concreta del flujo. Un UUID malformado devuelve 400 INVALID_REQUEST. |
name | string (≤ 255) | (ninguno) | Coincidencia por subcadena, sin distinguir mayúsculas de minúsculas, sobre el nombre del cliente. Casa tanto con customers_person.name (kyc, kyc_minor, kyc_representative) como con customers_company.company_name (kyb). |
tax_id | string | (ninguno) | Coincidencia exacta con el documento almacenado del cliente, del tipo que la plataforma tenga para él: CPF, CNPJ, DNI, CUIT, cédula de identidad, RUT o pasaporte. El valor se normaliza del lado del servidor a solo caracteres alfanuméricos, en mayúsculas, así que 1.234.567-2 coincide con el 12345672 almacenado. El ancho aceptado tras la normalización es de 6 a 14 caracteres, deliberadamente más amplio que un CPF o un CNPJ para que se puedan buscar los documentos de identidad argentinos y uruguayos; cualquier valor fuera de ese rango devuelve 400 INVALID_REQUEST antes de que se ejecute la consulta. |
tax_number | string | (ninguno) | Búsqueda por identificador fiscal: una ejecución de persona cuyo recorrido recogió ese CUIL o CUIT, o una ejecución de empresa cuyo company.tax_id es igual a ese valor (el CUIT, o el RUT de una empresa uruguaya). En un flujo que identifica a la persona por su DNI, también coincide con el CUIL enviado como primary.tax_id, que se guarda en el registro del cliente cuando la sesión lo crea o lo reutiliza (se envió primary.full_name, o el cliente ya existía), así esa sesión se encuentra antes de su paso de documento. Se normaliza como tax_id (solo caracteres alfanuméricos, en mayúsculas), así que 20-35398817-5 y 20353988175 son la misma búsqueda; cualquier valor fuera del rango de 6 a 14 caracteres tras la normalización devuelve 400 INVALID_REQUEST. Como reference_id, es una búsqueda por clave exacta y devuelve la fila en cualquier estado: sin status, la exclusión de estados terminales por defecto no se aplica; un status explícito sigue acotando. tax_id no se comporta así y sigue ocultando los estados terminales por defecto. Solo se sirve en las instancias que ejecutan esta versión de la superficie de integración: donde no está disponible, el parámetro se ignora y la respuesta no se filtra (consulte el aviso justo debajo de la tabla). |
classification | ocasional, habitual | (ninguno) | Filtra por la clasificación declarada al crear la sesión. Omitido, devuelve las dos clasificaciones y también las ejecuciones que no llevan clasificación alguna, que es el estado normal fuera del régimen uruguayo. |
tax_number solo filtra donde se sirve. El parámetro solo se sirve en las instancias que ejecutan esta versión de la superficie de integración, mientras que el listado en sí existe en todas. Una instancia que no lo sirve no lo rechaza: lo ignora y responde 200 con el listado sin filtrar, y la respuesta por sí sola no revela en cuál de los dos casos está. Antes de depender del parámetro, sondee cada instancia una vez, con la misma credencial que usa para el listado, enviando un valor deliberadamente inválido, como ?tax_number=12345. Un 400 INVALID_REQUEST indica que el parámetro se sirve y que todo listado con un tax_number válido se filtra por él; un 200 indica que allí se ignora, y el parámetro no debe usarse en esa instancia. Cualquier otro estado no dice nada sobre el parámetro. Como salvaguarda adicional, descarte un elemento cuyo identificador fiscal está presente y difiere del valor buscado, comparando ambos reducidos a caracteres alfanuméricos en mayúsculas (customer.tax_number en una fila de persona, company.tax_id en una fila de empresa); un elemento que no lleva ese valor no es evidencia en contra de la coincidencia (consulte customer.tax_number más abajo).
Los filtros son combinables: ?reference_id=SOL-2026-00042&status=approved devuelve las ejecuciones aprobadas cuyo reference_id coincide exactamente; ?name=silva&status=in_progress devuelve las ejecuciones en curso cuyo nombre de cliente contiene silva.
Respuesta (200)
Sección titulada «Respuesta (200)»{ "error": false, "data": { "items": [ { "execution_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "flow_type": "kyc_minor", "reference_id": "SOL-2026-00042", "status": "in_progress", "steps_completed": 5, "steps_total": 9, "customer": { "full_name": "Marcus Vinicius Sales", "tax_id": "23117555854", "tax_id_masked": "***5854", "email": "msales@exemplo.com.br", "is_pep": false, "has_foreign_tax_obligation": false }, "data_divergences": [], "created_at": "2026-03-31T09:00:00Z", "started_at": "2026-03-31T09:03:00Z", "expires_at": "2026-04-07T09:00:00Z", "link_expires_at": "2026-04-07T09:00:00Z", "links": { "url": "https://acme.onp.prod.guardline.com.br/onboarding/link/bcd234efg567", "embed_url": "https://acme.onp.prod.guardline.com.br/embed/t/bcd234efg567" }, "customer": { "full_name": "João Santos", "tax_id": "12345678909", "tax_id_masked": "***8909", "email": "joao@exemplo.com" } } ], "pagination": { "page": 1, "per_page": 20, "total": 1, "total_pages": 1 } }}Los metadatos de paginación están anidados bajo data.pagination (page, per_page, total, total_pages). El bloque decision es exclusivo del detalle: obténgalo mediante el endpoint de consulta individual.
El bloque customer tiene la misma forma en el listado y en el endpoint de detalle por id. En el listado solo vienen poblados los campos de identificación de resumen (full_name, tax_id, tax_id_masked, tax_number, email, is_pep, has_foreign_tax_obligation). Los subbloques de dirección, documento y biometría, más el envoltorio decision, solo los devuelve GET /api/v1/onboarding/executions/{execution_id}. El campo customer se omite (en lugar de emitirse como null) en las filas donde todavía no hay ningún registro de cliente vinculado (por ejemplo, sondeos o sesiones abandonadas antes del primer paso). En las ejecuciones kyb, tax_id es el documento de la empresa (el CNPJ, el CUIT o el RUT, según el país del flujo) y email se omite (las empresas no llevan email en el registro de cliente).
El campo tax_id es el documento sin formato: en una fila de persona, el documento de identidad (el CPF en los recorridos brasileños, el DNI o un pasaporte en los argentinos, la cédula o un pasaporte en el uruguayo), en una fila de empresa, la identificación fiscal de la empresa (el CNPJ, el CUIT o el RUT). Por defecto viaja completo. Un flujo cuya configuración activa integrator_list_masks_documents publica customer.tax_id y customer.tax_number de sus filas de persona enmascarados como tax_id_masked, solo en este listado; el endpoint de detalle los sigue devolviendo completos, y una fila de empresa nunca se enmascara. Donde el recorrido también recoge un identificador fiscal distinto del documento de identidad (el CUIL o el CUIT), ese valor viaja por separado en customer.tax_number. El tax_id_masked heredado se sigue emitiendo por compatibilidad hacia atrás y se retirará en una versión futura; las integraciones nuevas deben consumir tax_id.
customer.tax_number es el identificador fiscal que declaró la persona (el CUIL o el CUIT), con el mismo significado que el campo homónimo en Consulta de una ejecución, y solo se emite en las ejecuciones de persona. En una fila de empresa está ausente, aunque el bloque customer esté presente, porque el identificador fiscal de la empresa es company.tax_id. Trátelo como opcional: está ausente cuando el recorrido no lo recogió, y una fila encontrada mientras su paso de documento está reabierto para corrección puede volver sin él, porque el valor se lee solo de pasos completados. En un flujo que identifica a la persona por su DNI, una fila cuyos pasos completados no lo traen muestra en su lugar el CUIL guardado en el registro del cliente. Nunca crea un bloque customer: una fila que no tendría ese bloque sigue sin él, aunque la haya encontrado tax_number. Igual que el parámetro, solo se emite en las instancias que ejecutan esta versión de la superficie de integración.
Otros cinco campos viajan en cada elemento y es fácil pasarlos por alto:
| Campo | Descripción |
|---|---|
parent_execution_id | Nombra al padre en una fila de hijo vinculado (un kyb_representative o un representante de kyc_minor), de modo que el recorrido de un representante se correlaciona con el onboarding que lo originó. Ausente en las ejecuciones raíz. |
link_expires_at | La ventana del enlace para la fila, junto a expires_at. El mismo par, y por la misma razón, que en la lectura de detalle. |
links | El mismo bloque { url, embed_url } que en la lectura de detalle, construido a partir del enlace propio de la fila sin consulta adicional, y omitido del mismo modo cuando la fila nunca recibió un enlace. links.url es la forma de recuperar de nuevo el enlace del recorrido de una fila. |
company | Se emite en las filas KYB: solo legal_name y tax_id, ambos leídos de la proyección de la fila. |
data_divergences | Siempre se emite, y en el listado es siempre el arreglo vacío, sea lo que sea que la ejecución haya registrado. Las filas de divergencia por elemento serían una consulta por elemento, así que el listado no las obtiene. Lea las divergencias reales en el endpoint de detalle; no interprete un [] de aquí como “sin divergencias”. |
El tax_id_masked de un elemento del listado es la misma máscara que produce el endpoint de detalle: tres asteriscos seguidos de los últimos cuatro caracteres, así que 12345678909 se publica como ***8909 en los dos. Antes de esta versión el listado conservaba el medio de un documento de 11 dígitos (***456789**) y devolvía cualquier otro documento (un DNI, una cédula, un pasaporte) sin enmascarar. Trátelo como una cadena de presentación; nunca haga parsing de él.
En una fila KYB, customer y company están presentes los dos. El bloque customer refleja las proyecciones de la empresa por compatibilidad hacia atrás, así que customer.full_name lleva la razón social y customer.tax_id el documento de la empresa (el CNPJ, el CUIT o el RUT; nunca enmascarado, sea cual sea la configuración del flujo), los mismos valores que llevan company.legal_name y company.tax_id. La exclusividad mutua descrita en Consulta de una ejecución vale solo para la lectura de detalle.
Errores. Seis de los filtros se validan antes de que se ejecute la consulta (reference_id, flow_id, name, tax_id, classification y, donde se sirve, tax_number). Todos responden 400: classification con INVALID_CLASSIFICATION, los demás con INVALID_REQUEST. page y per_page se ajustan a valores seguros en lugar de rechazarse, y status se pasa tal cual.
| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_REQUEST | reference_id de más de 255 caracteres, un flow_id que no es un UUID, name de más de 255 caracteres, o tax_id o tax_number fuera del rango de 6 a 14 caracteres tras la normalización. El envoltorio no dice cuál de ellos se disparó. |
| 400 | INVALID_CLASSIFICATION | classification fuera de ocasional y habitual. |
| 401 | MISSING_TOKEN | No se envió ni X-API-Key ni Authorization. |
| 401 | INVALID_API_KEY | API Key inválida, revocada o expirada. |
| 502 | INTERNAL_ERROR | Falló el propio listado. |
Registro de clientes
Sección titulada «Registro de clientes»El grupo /api/v1/onboarding/customers publica el legajo del cliente sobre la
misma credencial que /flows y /sessions. Existe para dos usos que el recorrido
por sí solo no cubre: consultar lo que la plataforma guarda sobre una persona o
una empresa después de que el recorrido terminó, y abrir un recorrido nuevo sobre
un legajo que ya existe, sin pasar por el documento.
Es una superficie distinta de la del backoffice. El panel de cumplimiento lee el
legajo entero en /api/v1/customers/* con el JWT del panel; el integrador lee
aquí una proyección de él. Ambas delegan en el mismo servicio, pero la
proyección se copia campo por campo a propósito: un bloque de cumplimiento nuevo
(el anexo de PEP, el cuestionario FATCA, los certificados fiscales argentinos) no
llega a la aplicación del socio solo porque se agregó al legajo. Llegar exige que
alguien decida que llega.
Qué publica la lectura y qué retiene
Sección titulada «Qué publica la lectura y qué retiene»| Contenido | Tratamiento |
|---|---|
| Lo que el integrador envió, o lo que el cliente declaró sobre sí mismo | Publicado. El socio es el responsable de ese dato y por lo general tiene que mostrarlo de vuelta a quien lo tipeó. |
| Los veredictos de la plataforma, como códigos | Publicados: status_code, kyc_level_code y risk_level_code. Son lo que el socio necesita para decidir si la persona puede operar. |
| Los internos del motor | Retenidos. risk_score es el caso claro: la banda es el veredicto, el número es la calibración de una matriz que el integrador no configura y no puede interpretar. |
| Datos de terceros y el espacio de trabajo del analista | Retenidos: parientes PEP, vínculos familiares, nombre de la madre y del padre, documento del responsable, y las notes, que escribe la mesa de cumplimiento. |
El documento no se enmascara. Es la clave de correlación que el propio
integrador envió, y enmascararla era justamente lo que volvía inservible esta
lectura. Se publica como document más document_country, nunca como cpf: una
columna cuyo nombre afirma un documento brasileño es almacenamiento heredado, no
el contrato.
Consulta de personas
Sección titulada «Consulta de personas»GET /api/v1/onboarding/customers/personGET /api/v1/onboarding/customers/person/{id}| Parámetro | Descripción |
|---|---|
search | Documento, nombre o e-mail. |
page | Página, por defecto 1. |
per_page | Ítems por página, por defecto 20. |
El documento se almacena sin formato. Una búsqueda por 123.456.789-01 no
encuentra un legajo guardado como 12345678901: enviá solo los dígitos.
{ "error": false, "data": { "id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "document": "45678913", "document_country": "UY", "name": "Juan Pérez", "email": "juan@example.com", "phone": "+59899123456", "birth_date": "1990-05-20T00:00:00Z", "nationality": "Uruguaya", "occupation": "Contador", "activity_code": "620900", "activity_name": "Otras actividades de informática", "is_pep": false, "has_foreign_tax_obligation": false, "kyc_level_code": "basic", "status_code": "approved", "risk_level_code": "low", "is_active": true, "addresses": [], "created_at": "2026-09-04T13:02:11Z", "updated_at": "2026-09-07T09:41:52Z" }}| Campo | Descripción |
|---|---|
document, document_country | El documento de identidad tal como está almacenado, y la jurisdicción que lo emitió (ISO 3166-1 alpha-2, en mayúsculas). document_country se omite cuando el origen del cliente es desconocido; nunca trae otro formato. Sin enmascarar. |
name, email, phone, birth_date, nationality, gender | Lo que el integrador envió o el recorrido recolectó. |
profession, occupation, activity_code, activity_name, income | El bloque económico declarado. activity_code y activity_name salen del catálogo de actividad económica del país del flujo. |
guardian_name, guardian_relationship | Presentes en el legajo de un menor. El documento del responsable no viaja: es un tercero. |
is_pep, pep_condition | La marca declarada y la condición declarada. El anexo de parientes queda retenido, así que se puede saber que hubo declaración sin recibir la lista de familiares. |
has_foreign_tax_obligation | Obligación fiscal declarada en el exterior (FATCA / CRS). |
bank_accounts | Cuentas que el cliente declaró, donde el flujo las recolecta. |
kyc_level_code, status_code, risk_level_code | Los tres veredictos, como códigos. |
is_active | false después de una baja lógica. |
addresses | Domicilios registrados. Ausente cuando no hay ninguno. |
Consulta de empresas
Sección titulada «Consulta de empresas»GET /api/v1/onboarding/customers/companyGET /api/v1/onboarding/customers/company/{id}Los mismos parámetros de consulta; search coincide con el documento o la razón
social. La proyección trae document, document_country, company_name,
trade_name, main_activity, foundation_date, incorporation_place,
fiscal_residence_country, expected_monthly_volume, bank_accounts, los tres
códigos de veredicto, is_active, addresses y los dos timestamps.
Quedan retenidos el bloque de inscripción registral argentino (autoridad, libro, folio, tomo), los certificados fiscales y los metadatos de la declaración de beneficiario final: se recolectan para la mesa y para el regulador, y los beneficiarios son terceros.
El document_country de la empresa se deriva del país de residencia fiscal, que
es lo más parecido a una jurisdicción emisora que tiene este legajo. Queda por lo
tanto ausente, y no equivocado, para el tenant que no recolecta ese campo.
Creación de una persona
Sección titulada «Creación de una persona»POST /api/v1/onboarding/customers/personCrea el legajo sin ningún recorrido, para el alta que la aplicación del socio hace con lo que tiene a mano y completa después.
| Campo | Obligatorio | Descripción |
|---|---|---|
document | Sí | El documento de identidad. Es el único campo obligatorio porque es el identificador: es la clave natural del índice único, es por donde correlaciona la mesa, y es lo único que no se puede completar después sin que el legajo pase a ser otra persona. |
document_country | No | La jurisdicción emisora, ISO 3166-1 alpha-2. Ausente significa el valor por defecto de la instalación, que es Brasil: un alta uruguaya envía UY y recibe la validación de cédula en lugar de la de CPF. |
name | No | Cuando está ausente, el legajo queda con nombre vacío, y no con una etiqueta inventada. Un legajo creado acá puede no correr nunca un recorrido, y un nombre fabricado en un registro de cumplimiento sobrevive a la razón por la que se inventó. |
email, phone, birth_date, nationality | No | birth_date en AAAA-MM-DD. |
is_pep, has_foreign_tax_obligation | No | Booleanos declarativos. |
El legajo nace en nivel de KYC basic y estado pending, exactamente el estado en
el que un recorrido sin prellenado materializa a una persona que nunca vio: un
legajo creado por esta API y uno creado por un recorrido son la misma cosa en el
mismo punto de su vida.
Responde 201 con la proyección del legajo y un encabezado Location. Un segundo
POST con el mismo documento y el mismo país responde 409 ALREADY_EXISTS.
Actualización de una persona
Sección titulada «Actualización de una persona»PUT /api/v1/onboarding/customers/person/{id}Acepta name, email, phone, birth_date, nationality, gender,
profession, income e is_pep. Un campo ausente conserva el valor de la
columna; no hace falta reenviar el legajo entero.
Dos ausencias son deliberadas. El documento no es editable: cambiarlo no es una
edición, es otra persona, porque es la clave natural del índice único, es por donde
correlaciona la mesa y es contra lo que se corrió todo veredicto de screening ya
registrado. Un legajo con el documento equivocado es un legajo para crear de nuevo.
Y notes no es escribible acá: es el espacio de trabajo de la mesa de
cumplimiento, ya retenido en la lectura, y una superficie que no puede leer un
campo pero sí sobrescribirlo es peor que una que no hace ninguna de las dos cosas.
La misma línea acota el resto: esta ruta lleva dato de contacto y declarativo, del tipo que un cliente corrige sobre sí mismo. Lo que la plataforma verificó (el documento, la biometría, los veredictos de screening) se mueve solo por un recorrido.
El domicilio
Sección titulada «El domicilio»POST /api/v1/onboarding/customers/person/{id}/addressesEl domicilio es un recurso propio y no un bloque del cuerpo de la creación, porque un cliente puede tener más de uno y porque el legajo existe antes que cualquier dirección.
Campos: label, street (obligatorio), number, complement, neighborhood,
city (obligatorio), state_code (obligatorio), zip_code (obligatorio),
country, ibge_code e is_primary.
state_code y zip_code se validan por país: 8 dígitos y UF de 2 letras en
Brasil, 5 dígitos y nombre de departamento en Uruguay, CPA o 4 dígitos y provincia
en Argentina. country acepta cualquiera de las dos grafías ISO 3166-1, alpha-2 o
alpha-3, y se almacena como alpha-3.
Baja de un legajo
Sección titulada «Baja de un legajo»DELETE /api/v1/onboarding/customers/person/{id}La baja es lógica: el legajo pasa a is_active = false y se registra quién la
hizo. No hay borrado físico alcanzable por esta ruta. Tres consecuencias que vale
la pena dejar dichas:
- La baja libera el documento. Los índices únicos son parciales y solo valen para legajos activos, así que el mismo documento se puede crear de nuevo y dos filas históricas de la misma persona pasan a coexistir. Es deliberado. Un sistema que sincroniza con este y asume unicidad sin predicado entra en conflicto la primera vez que una persona se desactiva y se vuelve a dar de alta.
- Quien la ejecutó no queda nombrado cuando la llamada usa API key. La columna registra entonces que la baja vino de la superficie del socio y no de un analista con nombre. Sellarla con alguien que no la hizo sería peor.
- Una baja no es una purga. La conservación exigida es de no menos de diez años, y una baja lógica nunca debe ser el disparador de un descarte.
La empresa es de solo lectura en esta superficie. No hay creación, edición ni baja de empresa sobre la credencial del integrador: un legajo de KYB se arma a partir del estatuto y de la cadena societaria, no se tipea.
Abrir un recorrido para un cliente que ya existe
Sección titulada «Abrir un recorrido para un cliente que ya existe»POST /api/v1/onboarding/customers/person/{id}/sessionsPOST /api/v1/onboarding/customers/company/{id}/sessionsCuerpo: flow_id, obligatorio, más los opcionales que POST /sessions ya acepta
(reference_id, channel, redirect_url, notify, metadata, layout_mode,
surface_hint, classification, operation). La respuesta es el mismo envelope de
POST /sessions, sin cambios, con 201 y un encabezado Location que nombra la
ejecución.
Por qué existe, si el camino por documento ya reutiliza al cliente: la plataforma
ya publica customer_id en la respuesta de la sesión, así que hasta que esta ruta
salió entregaba un identificador que no aceptaba de vuelta. Y el socio que
deliberadamente no persiste dato personal tendría que guardar el documento para
nombrar a la persona; nombrarla por el id no lo exige.
| Regla | Respuesta |
|---|---|
| Gana el id: el prellenado se lee del legajo guardado y no hay reconciliación desde el cuerpo | Sin error. |
primary o company en el cuerpo | 400 FIELDS_NOT_ALLOWED_FOR_STEP_TYPE. Una segunda forma de nombrar al sujeto en la misma solicitud se resolvería en silencio a favor de la que el servicio lee primero, y quien llamó nunca se enteraría de que el nombre que envió fue ignorado. |
Bloque operation sin direction o sin wallet_type, o {} | 400 MISSING_REQUIRED_FIELDS, igual que en POST /sessions. Consulte Operación que abrió el onboarding. |
operation.direction u operation.wallet_type fuera de la lista | 400 VALIDATION_FAILED, igual que en POST /sessions. |
flow_id ausente | 400 MISSING_REQUIRED_FIELDS. flow_type no se acepta acá: resolver un tipo a “el flujo activo de esa clase” es una conjetura, y esta ruta existe para ser explícita. |
| Un id de persona en un flujo KYB, o un id de empresa en un flujo de persona | 422 CUSTOMER_FLOW_TYPE_MISMATCH. El sujeto de una ejecución es persona o empresa, nunca los dos; quien nombró una fila lo hizo a propósito. |
| Ningún legajo activo para ese id | 404 CUSTOMER_NOT_FOUND, distinto de FLOW_NOT_FOUND. Los dos pueden faltar en la misma solicitud, y quien llamó necesita saber qué mitad corregir. |
| Ya existe una sesión activa para esa persona y ese flujo | 409 ACTIVE_SESSION_EXISTS, con el enlace de retomada, igual que en POST /sessions. |
Estas dos rutas crean una sesión; el legajo no lo tocan. Y nunca las rechaza la política de cliente ya registrado.
Errores del grupo de legajos
Sección titulada «Errores del grupo de legajos»| HTTP | Código | Cuándo |
|---|---|---|
| 400 | INVALID_ID | El id del path no es un UUID. |
| 400 | INVALID_REQUEST | JSON mal formado, campo desconocido o tipo incorrecto. |
| 400 | VALIDATION_FAILED | Falla de campo, incluido un documento fuera del formato aceptado para el document_country declarado, o un código postal fuera del formato del país de la dirección. |
| 401 | MISSING_TOKEN, INVALID_API_KEY | Credencial ausente, inválida, revocada o vencida. |
| 404 | NOT_FOUND | Ningún legajo activo con ese id. |
| 404 | CUSTOMER_NOT_FOUND | Solo en las rutas de sesión por id: el cliente no existe (distinto de FLOW_NOT_FOUND). |
| 409 | ALREADY_EXISTS | Creación con un documento que ya pertenece a un legajo activo. |
| 409 | ACTIVE_SESSION_EXISTS | Solo en las rutas de sesión por id y de umbral. |
| 422 | CUSTOMER_FLOW_TYPE_MISMATCH | Solo en las rutas de sesión por id. |
| 429 | RATE_LIMITED | Límite de solicitudes superado. |
Cruce de umbral y step-up
Sección titulada «Cruce de umbral y step-up»POST /api/v1/onboarding/customers/person/{id}/thresholdsPOST /api/v1/onboarding/customers/company/{id}/thresholdsQué es. En regímenes donde la clasificación del cliente cambia después del
onboarding, por encima de un acumulado anual el cliente deja de ser ocasional y
el conjunto de información mínima del legajo crece (ver
Requisitos de información mínima). La
plataforma no ve ninguna transacción, así que queda exactamente una cosa que
necesita que le digan: que el cliente cruzó un escalón. Eso es lo que recibe
este endpoint, y es lo que abre el recorrido de step-up.
Qué no es. No es un feed transaccional, no calcula el acumulado, no trata una operación anulada o revertida y no convierte moneda.
Escalones aceptados, en dólares enteros:
threshold | Efecto | step_up |
|---|---|---|
1000 | Reabre el bloque habitual: el conjunto mínimo pasa de ocasional a habitual. | habitual_block |
15000 | El salto de nivel que pide origen de fondos. | level_two |
Cualquier otro valor responde 400: los demás escalones de la escala comercial no cambian lo que la plataforma pide mientras no haya feed transaccional.
Solicitud
Sección titulada «Solicitud»{ "threshold": 1000, "first_operation_date": "2026-03-14", "crossed_at": "2026-09-07T18:32:00-03:00", "idempotency_key": "<idempotency-key>", "flow_id": "61000000-0000-0000-0000-000000000080"}| Campo | Obligatorio | Descripción |
|---|---|---|
threshold | Sí | El escalón cruzado, en dólares enteros. Solo 1000 y 15000. |
first_operation_date | Sí | AAAA-MM-DD. El ancla de la ventana móvil de doce meses: el acumulado corre desde la fecha de la primera operación, y no por año calendario. Se graba una vez; un reporte posterior no mueve el ancla. |
crossed_at | Sí | RFC 3339 con offset. Es el reloj del socio, porque la plataforma no tiene transacción propia con la que fechar el cruce. Anterior a first_operation_date responde 400. |
idempotency_key | Sí | Hasta 255 caracteres. Es un campo del cuerpo, no el encabezado Idempotency-Key: el caché del encabezado vive 15 minutos y un cruce se reporta una vez en la vida del cliente, así que el reintento que importa llega horas después. Reutilizar la clave en otro cruce responde 409. |
flow_id | No | Sin él, la plataforma resuelve el flujo activo del tipo del cliente (kyc para persona, kyb para empresa). Existe para el tenant que publica más de un flujo por tipo y tiene que decir cuál abre el step-up. |
Respuesta
Sección titulada «Respuesta»201 cuando el reporte tuvo efecto, 200 cuando es una repetición y no pasó nada nuevo.
{ "error": false, "data": { "id": "1f0a9e60-0000-0000-0000-000000000000", "customer_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "customer_type": "person", "threshold": 1000, "currency": "USD", "step_up": "habitual_block", "classification": "habitual", "first_operation_date": "2026-03-14T00:00:00Z", "crossed_at": "2026-09-07T21:32:00Z", "idempotency_key": "<idempotency-key>", "execution_id": "5c2f31aa-0000-0000-0000-000000000000", "journey_url": "https://acme.onp.prod.guardline.com.br/onboarding/link/<token>", "replayed": false, "contract_version": 1, "created_at": "2026-09-08T12:03:11Z" }}| Campo | Descripción |
|---|---|
classification | Dónde deja el cruce al cliente en el eje ocasional / habitual. Los dos escalones responden habitual, y no es un descuido: 15.000 está por encima de 1.000. Lo que separa los escalones es step_up. |
execution_id | El recorrido de step-up que se abrió. Se crea por el mismo camino que usa POST /customers/{person|company}/{id}/sessions, direccionado por el id del cliente, así que el prellenado viene del legajo guardado. |
journey_url | Presente solo en la llamada que abrió el recorrido. El enlace se acuña ahí y no queda guardado en el cruce, así que una repetición responde execution_id y no responde URL. |
replayed | true cuando este POST no tuvo efecto: el cruce ya estaba registrado, y no se produjo ni un segundo recorrido ni un segundo webhook. Acompaña al 200. |
contract_version | La versión de esta propuesta. |
Notificación. La plataforma no notifica al cliente final por su cuenta en este camino. La aplicación del socio es dueña del canal de contacto en esta integración y recibe la URL del recorrido en la respuesta y en el webhook; una notificación nuestra acá sería un segundo mensaje sobre un evento que el socio ya está atendiendo.
Idempotencia durable. Es la única ruta de la API cuya idempotencia no es la del
encabezado: la clave viaja en el cuerpo y se convierte en fila, con unicidad por
cliente y escalón. Una repetición responde 200 con replayed: true y el cruce
original, sin abrir un segundo recorrido y sin emitir un segundo webhook. Un cruce
grabado cuyo recorrido nunca llegó a abrirse no se trata como repetición: el
reintento termina el trabajo, que es la razón por la que la fila se graba antes que
el recorrido.
Errores
Sección titulada «Errores»| HTTP | Código | Cuándo |
|---|---|---|
| 400 | INVALID_REQUEST | JSON mal formado o tipo equivocado. |
| 400 | VALIDATION_FAILED | threshold fuera de la escala, fecha en un formato distinto del declarado, crossed_at anterior a first_operation_date, flow_id que no es UUID o flujo inactivo. |
| 400 | INVALID_ID | El id del path no es un UUID. |
| 404 | NOT_FOUND | Ningún cliente activo con ese id, o ningún flujo activo del tipo para abrir el step-up. |
| 409 | ALREADY_EXISTS | La idempotency_key ya fue usada en otro cruce (de otro cliente). |
| 409 | ACTIVE_SESSION_EXISTS | El cruce queda registrado, pero el step-up no se abrió porque el cliente todavía tiene un recorrido abierto en el mismo flujo (normalmente la sesión del cold-start que creó al cliente; un recorrido activo por cliente y flujo). Mismo cuerpo que el 409 de POST /sessions: execution_id del recorrido abierto y, mientras su enlace siga vigente, resume_url y resume_embed_url. No se envía webhook. Repetir el mismo reporte (misma idempotency_key) cuando ese recorrido se cierre abre el step-up y responde 200 con replayed: true y el execution_id nuevo; enviar el flow_id de otro flujo del mismo tipo también lo abre. |
Documentos del legajo
Sección titulada «Documentos del legajo»GET /api/v1/onboarding/executions/{execution_id}/documentsLista todos los documentos que acumuló el caso: los de la empresa, los que cada representante legal cargó dentro de su propia jornada y los que pertenecen a una persona declarada (beneficiarios finales y autoridades del acta). Solo metadatos; el contenido lo sirve el endpoint de descarga descrito más abajo.
El execution_id puede ser el de la ejecución raíz o el de cualquier ejecución hija vinculada a ella. En ambos casos la respuesta es el caso completo, porque un integrador que tiene el identificador de la jornada de un representante está haciendo la misma pregunta que quien tiene el de la empresa.
Los dos endpoints de este capítulo solo se sirven en las instancias que ejecutan esta superficie; en una instancia que no lo hace responden 404 sea cual sea el alcance de la credencial, así que conviene confirmar la disponibilidad antes de programar contra ellos.
Por qué una sola llamada
Sección titulada «Por qué una sola llamada»Los archivos de un caso viven en tres lugares distintos, y la división no es arbitraria: las imágenes capturadas en la jornada de la empresa pertenecen a la ejecución raíz, las capturadas en la jornada de un representante pertenecen a su ejecución hija, y el documento de una persona declarada no pertenece a ninguna jornada, ya que un beneficiario final puede ser declarado sin recorrer nunca ninguna. Este endpoint recorre los tres y devuelve el conjunto, de modo que el integrador pide el legajo y no sus partes.
Respuesta (200)
Sección titulada «Respuesta (200)»{ "error": false, "data": [ { "document_id": "exi_2a7ce1ea-7c87-41b2-bf3d-64642985c55f", "kind": "estatuto", "scope": "company", "subject": { "type": "company", "id": "4992a56f-79b4-4d7e-9414-30506a4528d2", "name": "Brisbane SA", "tax_number": "30715563205" }, "execution_id": "236ea716-86c3-4529-9dd7-5db6251e87f9", "mime_type": "application/pdf", "size_bytes": 1843200, "sha256": "6c41cf84eeab...9ce11b62", "captured_at": "2026-09-04T09:58:04-03:00", "content_url": "/api/v1/onboarding/executions/236ea716-.../documents/exi_2a7ce1ea-.../content" }, { "document_id": "rpd_b8def593-83d3-4dd8-b201-84eead155e2d", "kind": "dni_front", "scope": "representative", "subject": { "type": "person", "id": "30abffec-d0ec-4fed-a99b-bbd0a1d1dbac", "name": "Ana Perez", "tax_number": "20695350854" }, "mime_type": "image/jpeg", "size_bytes": 284120, "sha256": "12ebef354bee...6011f1d2", "captured_at": "2026-09-04T09:58:04-03:00", "content_url": "/api/v1/onboarding/executions/236ea716-.../documents/rpd_b8def593-.../content" } ]}| Campo | Descripción |
|---|---|
document_id | Identificador opaco del documento. Úselo tal cual: el prefijo distingue los orígenes internos, no forma parte del contrato y no debe ser interpretado ni construido por el integrador. |
kind | Qué documento es. El conjunto depende de lo que recolecte la jornada: en los flujos KYB de la Argentina aparecen estatuto, acta, balance, poder, constancia_uif y los formularios OCDE; en las jornadas de persona, document_front, document_back y selfie; en los documentos por persona, dni_front, dni_back, supporting y registro_firma. |
scope | A qué parte del caso pertenece el documento: company, representative o applicant. |
subject | De quién es el documento. type es company o person; id es el identificador de la empresa o del representante; name es el nombre, cuando está registrado; tax_number es el identificador descrito justo debajo de esta tabla. Un sujeto eliminado a pedido del titular conserva type e id y pierde las etiquetas, porque los documentos siguen perteneciendo a ese caso. |
execution_id | La jornada donde se capturó el archivo: la raíz del caso en los documentos de la empresa, la ejecución del representante en los que él cargó. Ausente en los documentos ligados a una persona, que no pertenecen a ninguna jornada. |
mime_type / size_bytes | Tipo y tamaño del archivo tal como está almacenado. |
sha256 | Hash del contenido, registrado al momento de la carga. Sirve para verificar la descarga y para saber si el archivo ya está en poder del integrador. |
captured_at | Cuándo se cargó el archivo. |
content_url | Ruta de descarga ya armada para ese documento. |
Qué lleva subject.tax_number. Depende del sujeto:
- En un sujeto
company, el registro de la empresa, sin cambios: el mismo valor quecompany.tax_idlleva en el resto de la API (el CNPJ, el CUIT o el RUT). - En un sujeto
person, el identificador fiscal (el CUIL, o el CUIT cuando la persona declaró uno) leído del paso de documento completado de las ejecuciones de esa persona en este caso. Cuando más de una lo lleva, vale el de la ejecución más reciente para todos los documentos de esa persona, incluidos los capturados en una ejecución más antigua. Por eso solo coincide con elcustomer.tax_numberde Consulta de una ejecución y de Listado de ejecuciones cuando esa persona tiene una única ejecución en el caso con identificador fiscal; no lo use para relacionar documentos con ejecuciones. Un documento capturado en una jornada indica su ejecución enexecution_id; un documento ligado a la persona no pertenece a ninguna ejecución y se vincula con la persona porsubject.id. - En cualquier otra situación, el documento registrado para esa persona, que no es necesariamente un identificador fiscal: el documento de identidad que recogió la jornada de la propia persona (el DNI, la cédula o un pasaporte) cuando ningún paso de documento completado de este caso lleva CUIL ni CUIT, lo que incluye la jornada cuyo paso de documento todavía no se completó o se reabrió para corrección y los flujos que no recogen CUIL ni CUIT (por ejemplo, el uruguayo); el documento informado en la declaración (un DNI o un CUIT) para la persona que la empresa declaró sin jornada propia. Trátelo como el mejor identificador disponible para esa persona. El campo se omite cuando no hay nada registrado, lo que incluye los documentos de una jornada cuyo registro de persona todavía no existe, aunque el
customer.tax_numberde esa ejecución ya lleve un identificador fiscal. - Nunca un valor de otro caso. Un identificador fiscal que la misma persona informó en un caso distinto no aparece en este listado.
En el ejemplo, el CUIL de Ana Perez supone que recorrió su propia jornada en este caso y que esa jornada está registrada bajo la misma persona que la declaración (se informó el mismo número de documento en ambas); su dni_front es un documento ligado a la persona (sin execution_id) y entonces lleva el mismo valor que sus documentos de jornada. De lo contrario, la misma fila lleva el documento informado en la declaración.
El orden es estable: primero los documentos del titular del caso, la empresa en un KYB y el propio solicitante en un KYC, después los de cada representante, y dentro de un sujeto por tipo de documento. Dos listados del mismo caso no cambian filas de lugar sin motivo.
Descarga del contenido
Sección titulada «Descarga del contenido»GET /api/v1/onboarding/executions/{execution_id}/documents/{document_id}/contentDevuelve el archivo tal como fue cargado, con Content-Type, Content-Disposition y ETag. El ETag es el sha256 publicado en el listado, así que reenviarlo en If-None-Match responde 304 Not Modified sin cuerpo. Mientras los archivos se almacenen junto al registro, esa solicitud condicional es la diferencia entre una sincronización barata y una cara.
El documento tiene que pertenecer al caso del execution_id de la ruta. El conjunto que arma el listado es también la frontera de autorización: un identificador válido de otro caso responde 404, y no el archivo.
Autenticación y alcance
Sección titulada «Autenticación y alcance»Los dos endpoints exigen que la clave de API lleve el alcance documents:read. Es la misma credencial que el resto de las rutas de /api/v1/onboarding, descrita en Autenticación, así que no hace falta emitir ninguna credencial nueva y el valor ya desplegado del lado del integrador no cambia; lo que cambia es el conjunto de permisos asociado a ella. Una clave que funciona en todas las demás rutas sigue respondiendo 403 aquí mientras ese alcance no esté en ella.
Quién concede el alcance depende del momento en que se pide. En las instancias que sirven esta superficie, una clave creada en el panel administrativo de Guardline lleva onboarding:read y onboarding:write por defecto, y documents:read es una casilla aparte que el operador marca al momento de la creación; marcar esa casilla es todo el trabajo. Para una clave que ya está en poder del integrador no hay pantalla de edición, así que el alcance lo agrega el soporte de Guardline sobre la clave existente: el valor sigue siendo el mismo, y no hay que cambiar ni volver a desplegar nada del lado del integrador.
La única excepción es una clave emitida antes de que los alcances se exigieran: un token sin ningún alcance declarado es irrestricto y llega a estos dos endpoints como llega a todas las demás rutas. Es una forma heredada, y ninguna clave creada hoy por el panel sale con la lista de alcances vacía. Declarar alcances es la forma de restringir un token a propósito, útil cuando el acceso a documentos tiene que ser revocable por separado del resto de la integración.
Errores
Sección titulada «Errores»| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_ID | El execution_id de la ruta no es un UUID válido. |
| 400 | INVALID_REQUEST | El document_id no tiene el formato de un identificador de documento. |
| 401 | MISSING_TOKEN | No se envió ninguna credencial. |
| 401 | INVALID_API_KEY | API key inválida, revocada o vencida. |
| 403 | FORBIDDEN | El token declara alcances y ninguno de ellos es documents:read. La credencial en sí es válida: hay que pedirle al soporte de Guardline que agregue el alcance a la clave que ya está en uso, en vez de emitir una nueva. |
| 404 | EXECUTION_NOT_FOUND | No existe ninguna ejecución con ese execution_id. |
| 404 | NOT_FOUND | El documento no pertenece a este caso. |
| 409 | INTEGRITY_TAMPERED | El contenido almacenado no coincide con el hash registrado al momento de la carga. El archivo no se entrega. |
| 429 | RATE_LIMITED | Se superó el límite de solicitudes. |
| 500 | INTERNAL_ERROR | Cualquier otra falla al armar el conjunto o leer el archivo. |
Notas de consumo
Sección titulada «Notas de consumo»Los documentos aparecen a medida que se cargan, así que el legajo está completo recién cuando termina la jornada; una consulta anterior devuelve lo que exista en ese momento.
El almacenamiento guarda un archivo por tipo y por jornada: una carga repetida del mismo tipo reemplaza a la anterior en ese lugar, en vez de acumularse.
Cuentas abiertas a partir de un alta
Sección titulada «Cuentas abiertas a partir de un alta»Tres rutas le permiten al core bancario reflejar en la plataforma las cuentas que abrió a partir de una ejecución y confirmar la bancarización, que vuelve operativa una cuenta del alta simplificada. La ejecución del path es el alcance: una cuenta de otra ejecución responde 404, igual que una cuenta que no existe.
POST /api/v1/onboarding/executions/{execution_id}/accountsGET /api/v1/onboarding/executions/{execution_id}/accountsPOST /api/v1/onboarding/executions/{execution_id}/accounts/{account_id}/bancarizationLas dos escrituras aceptan solo API key. Un JWT de backoffice, de cualquier rol, recibe 403 FORBIDDEN. La lectura acepta API key o JWT. Ninguna de las dos escrituras usa el header Idempotency-Key: las dos son idempotentes en la base de datos, por las claves que se describen abajo.
Registro de una cuenta
Sección titulada «Registro de una cuenta»{ "external_ref": "0012-3456789", "account_type_code": "CA" }| Campo | Obligatorio | Descripción |
|---|---|---|
external_ref | Sí, hasta 64 caracteres | Identificador de la cuenta en el core. Junto con la ejecución, es la clave de idempotencia. |
account_type_code | No, hasta 30 caracteres | Tipo de cuenta. |
El cuerpo no acepta estado: lo decide la plataforma, y una cuenta del circuito de alta simplificada nace no_operativa, mientras que las demás nacen sin estado (status: null). Responde 201 con la cuenta cuando es nueva y 200 con la cuenta ya registrada cuando se reenvía el mismo external_ref para la misma ejecución. Ninguno de los dos es un error.
Consulta de las cuentas
Sección titulada «Consulta de las cuentas»Responde 200 con todas las cuentas de la ejecución en data, cada una con el historial completo de transiciones en transitions, sin paginación. Una ejecución sin cuentas responde una lista vacía; una ejecución que no existe responde 404.
Confirmación de la bancarización
Sección titulada «Confirmación de la bancarización»{ "operation_key": "BANC-2026-000123", "occurred_at": "2026-09-16T13:40:00Z", "source_kind": "channel", "source_ref": "home-banking", "parameter_ref": "RN-062", "evidence": { "operation_id": "987654" }}| Campo | Obligatorio | Descripción |
|---|---|---|
operation_key | Sí, hasta 100 caracteres | Clave de la operación que volvió operativa la cuenta. Junto con la cuenta, es la clave de idempotencia. |
occurred_at | No | Instante RFC 3339 en que ocurrió la operación. |
source_kind | No | channel o system. user se rechaza en esta ruta. |
source_ref, parameter_ref | No, hasta 100 y 50 caracteres | Referencias libres al origen y al parámetro aplicado. |
evidence | No | Objeto libre que se guarda con la transición. |
Responde 201 con la transición cuando se registra y 200 con la transición existente cuando se reenvía la misma operation_key. La transición registra la credencial que la envió en recorded_by_token_id.
Errores de las cuentas
Sección titulada «Errores de las cuentas»| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_REQUEST | Cuerpo JSON mal formado. |
| 400 | VALIDATION_FAILED | Falta un campo obligatorio, se superó un largo, o source_kind está fuera de la lista. |
| 403 | FORBIDDEN | Escritura llamada con JWT, o API key cuyos alcances no incluyen el exigido. |
| 404 | NOT_FOUND | La ejecución no existe, o la cuenta no existe o pertenece a otra ejecución. |
| 422 | UNPROCESSABLE | La cuenta ya es operativa con otra operation_key, no pertenece al circuito de alta simplificada, o es una cuenta de empresa. |
Cancelación de una ejecución
Sección titulada «Cancelación de una ejecución»POST /api/v1/onboarding/executions/{execution_id}/cancelPOST /api/v1/executions/{execution_id}/cancelLas dos rutas ejecutan la misma operación, con las mismas reglas y la misma respuesta. Prefiera la ruta /onboarding, junto a las demás llamadas de su integración; la ruta /executions se mantiene para las integraciones que ya la usan. Antes de esta versión solo existía la segunda, y la primera respondía 404 ROUTE_NOT_FOUND.
Lleva la ejecución al estado cancelled. Cuando la ejecución tiene ejecuciones hijas (el representante bajo un padre kyc_minor), la llamada se propaga en cascada y las cancela en la misma operación.
Como el índice único parcial idx_executions_active_person_flow excluye los estados terminales, cancelar libera el hueco (customer_person_id, flow_id). Tras una cancelación exitosa, un nuevo POST /onboarding/sessions para el mismo CPF y el mismo flujo deja de devolver 409 ACTIVE_SESSION_EXISTS y se puede crear una sesión nueva. Casos de uso típicos: reabrir un recorrido kyc_minor para volver a probarlo, recuperarse de un envío incorrecto o rotar una sesión que el usuario final no pudo terminar a tiempo.
Cuando la operación tiene éxito se emite un evento de webhook onboarding.cancelled; consulte Familias de eventos.
Autenticación
Sección titulada «Autenticación»Acepta cualquiera de estas dos opciones:
X-API-Key(gl_live_*ogl_test_*), oAuthorization: Bearer <JWT>(JWT del backoffice).
Una API key que declara alcances necesita onboarding:write; una clave sin alcances declarados no tiene restricciones. Un JWT del backoffice necesita el rol admin o supervisor.
Idempotencia
Sección titulada «Idempotencia»Respeta el encabezado Idempotency-Key; consulte Idempotencia.
Cuerpo de la solicitud
Sección titulada «Cuerpo de la solicitud»El cuerpo es opcional. El único campo aceptado es reason, que se registra en la traza de auditoría y en el payload del evento onboarding.cancelled:
{ "reason": "Duplicate registration, recreating with correct data"}| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
reason | string (máx. 500) | No | Motivo en texto libre registrado en la auditoría y en el payload del evento. |
Respuesta de éxito (200)
Sección titulada «Respuesta de éxito (200)»{ "error": false, "data": { "execution_id": "dc8fc63f-f25a-4ce7-a9a7-2005bb3f1689", "cancelled_children": [ "b11d2b44-3a8e-4a0f-9c4d-90d1f8a2e111" ], "status_code": "cancelled", "cancelled_at": "2026-05-27T13:50:00Z" }}| Campo | Tipo | Descripción |
|---|---|---|
execution_id | UUID | Ejecución que se canceló. |
cancelled_children | arreglo de UUID | UUID de las ejecuciones hijas canceladas en cascada (el representante bajo un kyc_minor). La clave se omite por completo, no se envía como [], cuando no hubo cascada: sin hijos, o sin ninguno que siguiera siendo cancelable. Compruebe la presencia de la clave antes de leer su longitud. |
status_code | string | Siempre cancelled cuando la operación tiene éxito. |
cancelled_at | ISO 8601 | Marca de tiempo de la cancelación. |
Errores
Sección titulada «Errores»| HTTP | Código | Causa |
|---|---|---|
| 400 | INVALID_ID | El UUID de la ruta está malformado. |
| 400 | INVALID_REQUEST | El cuerpo de la solicitud está presente pero malformado. |
| 400 | VALIDATION_FAILED | reason tiene más de 500 caracteres. |
| 401 | MISSING_TOKEN / INVALID_API_KEY | Autenticación ausente, inválida o expirada. |
| 403 | FORBIDDEN | API key sin onboarding:write, o JWT del backoffice con un rol distinto de admin o supervisor. |
| 404 | NOT_FOUND | No existe ninguna ejecución con ese UUID. |
| 409 | EXECUTION_TERMINAL | La ejecución ya está en un estado terminal. El conjunto de rechazo son exactamente cuatro códigos: approved, rejected, expired y cancelled. |
| 429 | RATE_LIMITED | Se superó el límite de solicitudes. |
La cancelación acepta un conjunto de estados más amplio de lo que sugiere la columna Terminal de la tabla del ciclo de vida. blocked es uno de ellos: cancelar una ejecución blocked tiene éxito y responde 200, no 409. Lo mismo ocurre al cancelar una en completed, y una en processing, pending_review o pending_representative. El conjunto aceptado completo es created, in_progress, pending_review, pending_representative, processing, completed y blocked.
Webhooks
Sección titulada «Webhooks»Los webhooks son notificaciones HTTP que Guardline envía al backend del integrador cuando una ejecución cambia de estado. Los payloads son intencionadamente escuetos: contienen los identificadores del evento y un pequeño bloque de datos. Los datos completos deben obtenerse mediante el endpoint de consulta.
Familias de eventos
Sección titulada «Familias de eventos»Tras la reescritura conviven dos familias:
onboarding.*: estado del onboarding en su conjunto. Se emite en todas las ejecuciones.linkage.*: estado de un sujeto vinculado dentro de un flujo emparejado. Cubre al representante legal dekyc_minory a los representantes legales declarados en un padrekyb. Se emite en la ejecución padre, con el resultado del sujeto vinculado reflejado en el payload.
La mesa de cumplimiento emite una tercera familia, cmp.case.*, que es interna y no llega al integrador. Se la nombra aquí solo para que su ausencia en su endpoint no se lea como una entrega perdida.
Eventos onboarding.*
Sección titulada «Eventos onboarding.*»| Evento | Descripción |
|---|---|
onboarding.started | Sesión creada mediante POST /sessions. |
onboarding.completed | Todos los pasos enviados; el motor de decisión está procesando. |
onboarding.approved | Aprobado por el motor o por un analista. |
onboarding.rejected | Rechazado por el motor o por un analista. |
onboarding.review | Derivado a revisión manual. |
onboarding.blocked | Recorrido interrumpido por una regla de seguridad. |
onboarding.expired | Sesión expirada antes de completarse. |
onboarding.cancelled | Cancelado por el integrador o por un administrador. |
onboarding.documents_requested | Un analista reabrió pasos concretos y reemitió el enlace. Ver más abajo. |
onboarding.threshold_crossed | El integrador reportó que un cliente cruzó un escalón del acumulado anual y la plataforma abrió el recorrido de step-up. Se emite una vez por cruce: repetir el POST no dispara un segundo evento. Contrato propuesto, ver Cruce de umbral y step-up. |
onboarding.documents_requested
Sección titulada «onboarding.documents_requested»El único evento que pide al integrador que actúe en lugar de limitarse a informar un resultado. Se dispara cuando un analista del backoffice reabre pasos concretos de una ejecución bajo revisión, lo que rota el enlace y reinicia su reloj. El payload lleva lo que el integrador necesita para hacer volver al cliente sin consultar nada:
{ "event": "onboarding.documents_requested", "execution_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "flow_type": "kyc", "reference_id": "SOL-2026-00042", "timestamp": "2026-05-13T12:49:34Z", "data": { "reason": "Foto do documento ilegível", "requested_steps": ["document-front", "document-back"], "resume_url": "https://acme.onp.prod.guardline.com.br/onboarding/link/<token>", "link_expires_at": "2026-05-20T12:49:34Z" }}resume_url viaja en el payload porque el enlace fue rotado: la URL que el integrador ya tenía deja de ser la que hay que enviar. reference_id recae en el id de la ejecución cuando la sesión se creó sin uno, así que el campo nunca es una cadena vacía.
Eventos linkage.* (kyc_minor)
Sección titulada «Eventos linkage.* (kyc_minor)»| Evento | Descripción |
|---|---|
linkage.pending | El menor completó su recorrido y el padre pasó a pending_representative. La URL del representante se creó en POST /sessions; este evento marca la transición del padre, no la creación del hijo. |
linkage.started | El representante legal accedió a su enlace y envió el primer paso. |
linkage.completed | El representante legal completó su recorrido, lo que dispara la consolidación del padre. El evento se emite antes de que se evalúen las validaciones de la consolidación, así que no garantiza que la consolidación se haya ejecutado: cuando una validación falla, no hay consolidación. |
linkage.rejected | Un analista rechazó la ejecución del representante en la revisión manual. |
linkage.expired | La ejecución del representante expiró antes de completarse. |
En un padre kyb estos eventos no siguen a los representantes de forma individual. linkage.started y linkage.completed se deduplican contra una única columna de estado del padre, no una por representante. Con varios representantes declarados, el primero en enviar un paso emite linkage.started y los demás no emiten nada; el primero en terminar emite linkage.completed y los demás no emiten nada, salvo que antes un representante posterior devuelva el estado del padre y vuelva a armar el par.
Por lo tanto la cantidad de eventos es función de cómo se entrelazan los representantes, no de cuántos se declararon. No cuente eventos para inferir el progreso. Consulte el endpoint de representantes, que reporta el estado de cada representante directamente y es la fuente confiable del progreso por representante.
Eventos representative.* heredados
Sección titulada «Eventos representative.* heredados»La reescritura de la API para integradores reemplazó representative.{pending, started, completed} por la familia linkage.* anterior. Los tipos de evento heredados no se emiten en las rutas de código nuevas, pero las entregas históricas de webhooks en los logs de auditoría del integrador pueden referenciarlos. Trate ambas taxonomías como equivalentes en información para los datos históricos; hoy solo se emite linkage.*.
Orden de emisión
Sección titulada «Orden de emisión»Los eventos siguen un orden de emisión estricto dentro de cada ejecución. Debido a los reintentos, los eventos pueden llegar desordenados al endpoint del integrador. El campo event_sequence (monótonamente creciente por ejecución) debe usarse como criterio canónico de ordenación, no el timestamp ni el orden de entrega. Para reconocer un evento repetido, en cambio, use el event_id del cuerpo; consulte Deduplicación.
KYC estándar
Sección titulada «KYC estándar»Aprobación automática:
onboarding.started (1) session createdonboarding.completed (2) all steps submittedonboarding.approved (3) approved by engineRechazo:
onboarding.started (1)onboarding.completed (2)onboarding.rejected (3)Revisión manual y posterior resolución:
onboarding.started (1)onboarding.completed (2)onboarding.review (3)onboarding.approved (4) analyst decision (or onboarding.rejected)Bloqueado:
onboarding.started (1)onboarding.blocked (2)Expirado:
onboarding.started (1)onboarding.expired (2)KYC Minor
Sección titulada «KYC Minor»Los eventos linkage.* referencian el execution_id del padre (el del menor), así que el progreso del representante se expone en la cadena del padre. Eso no vale para todos los eventos que produce el emparejamiento: un hijo representante emite sus propios onboarding.expired, onboarding.approved y onboarding.rejected sobre el execution_id del hijo.
Como event_sequence se calcula por id de ejecución, esos eventos del hijo caen en una cadena de secuencia aparte que reinicia en 1. Un integrador que ordena por event_sequence y se indexa por el padre verá un evento suelto que lleva un execution_id desconocido y la secuencia 1. Esos payloads llevan parent_execution_id, que es el campo por el que correlacionar.
Aprobación automática:
onboarding.started (1) session createdlinkage.pending (2) minor completed; parent in pending_representativelinkage.started (3) representative accessed their linklinkage.completed (4) representative finishedonboarding.completed (5) consolidation; decision engine processingonboarding.approved (6) approved by engineRechazo:
onboarding.started (1)linkage.pending (2)linkage.started (3)linkage.completed (4)onboarding.completed (5)onboarding.rejected (6)Revisión manual y posterior resolución:
onboarding.started (1)linkage.pending (2)linkage.started (3)linkage.completed (4)onboarding.completed (5)onboarding.review (6)onboarding.approved (7) analyst decision (or onboarding.rejected)Bloqueado durante el módulo del menor:
onboarding.started (1)onboarding.blocked (2)Expirado a la espera del representante:
onboarding.started (1)linkage.pending (2)onboarding.expired (3) parent expiredlinkage.expired (4) representative's link expiredEl onboarding.expired se despacha antes que el linkage.expired, no después.
Representante rechazado por un analista:
onboarding.started (1)linkage.pending (2)linkage.started (3)linkage.completed (4)onboarding.completed (5)onboarding.review (6)onboarding.rejected (7) parent decisionlinkage.rejected (8) analyst rejected the representativeIgual que con la expiración, el onboarding.rejected se despacha antes que el linkage.rejected.
Payload
Sección titulada «Payload»{ "event": "onboarding.approved", "execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "flow_type": "kyc_minor", "reference_id": "SOL-2026-00042", "event_id": "7d3f2a9c-5b1e-4c8a-9f0d-2e6b4a8c1d35", "event_sequence": 6, "timestamp": "2026-04-01T10:45:00Z", "data": { "decision": { "result": "approved", "risk_level": "low", "decided_by": "engine", "decided_at": "2026-04-01T10:45:00Z" } }}| Campo | Descripción |
|---|---|
event | Tipo de evento. |
execution_id | Identificador de la ejecución padre. En los eventos linkage.* de kyc_minor, este es el ID de ejecución del menor. El identificador del hijo vinculado no viaja en el bloque data, ni en este evento de vinculación ni en ningún otro. En un padre kyb los ids de los hijos se alcanzan de forma indirecta mediante data.legal_representatives; en kyc_minor no existe ese camino, así que un consumidor que necesite el id del hijo tiene que obtenerlo por su cuenta. |
flow_type | Tipo de recorrido. |
reference_id | Identificador del integrador, tal como se envió al crear la sesión. |
event_id | UUID del evento, el mismo valor que el encabezado X-Guardline-Event-ID. Viaja dentro del cuerpo firmado, así que la firma HMAC lo cubre, y es idéntico en todos los reintentos de la misma entrega. Es la clave de deduplicación preferida. Las entregas anteriores a la introducción del campo no traen la clave; consulte Deduplicación. |
event_sequence | Número secuencial del evento dentro de la ejecución, para la ordenación canónica. Junto con execution_id identifica el evento de forma única, y es la clave de deduplicación alternativa cuando falta event_id. |
timestamp | Marca de tiempo de emisión del evento en ISO 8601. |
data | Bloque aditivo, ausente cuando quedaría vacío. Contenido más abajo. |
El bloque data
Sección titulada «El bloque data»Todo lo que hay aquí es aditivo: una clave sencillamente está ausente cuando no hay nada que reportar, de modo que un receptor escrito contra un envoltorio más antiguo sigue funcionando.
| Clave | Cuándo está presente | Contenido |
|---|---|---|
data.decision | Una vez que la ejecución tiene una decisión, y en varios eventos que no tienen ninguna | result, risk_level, decided_by, decided_at. result no siempre contiene una decisión; ver más abajo. |
data.device | En todos los eventos excepto onboarding.started, cuando el recorrido tiene un perfil de dispositivo legible | El mismo bloque de riesgo de dispositivo que en la lectura de la ejecución (consulte Bloque device). onboarding.started se omite a propósito: se dispara al crear la sesión, antes de que el perfilador del navegador se haya ejecutado, así que la lectura está garantizada vacía. |
data.company | En los eventos del padre kyb | tax_id, legal_name y trade_name cuando se conocen. |
data.legal_representatives | En los eventos del padre kyb | Una entrada por representante declarado: execution_id, status, flag (true en todo estado terminal que no sea una aprobación, es decir rejected, blocked, pending_review y expired, para que un consumidor pueda detectar al representante que bloquea sin evaluar él mismo los estados), decision y risk_score (el veredicto del propio representante, aditivos y omitidos mientras no tiene decisión), más reference_id, full_name, name, surname y document cuando el registro del representante los lleva. |
La forma KYB es deliberada: un evento de empresa lleva la identidad de la empresa y todos sus representantes en un mismo payload, de modo que el integrador recibe la empresa completa de una sola vez en lugar de correlacionar N eventos por ejecución. Solo el padre kyb lo emite; los hijos son ejecuciones kyb_representative y emiten sus propios eventos ordinarios.
Pese al nombre, data.decision no siempre lleva una decisión. En varios eventos result lleva en cambio un estado de ejecución: allí aparecen pending_representative, in_progress, completed, blocked, cancelled y expired. Esos mismos eventos envían risk_level: "low", que es un valor por defecto y no una evaluación de riesgo real. No ramifique sobre result contra el vocabulario de decisiones a secas, y no lea risk_level como una evaluación salvo que result contenga realmente una decisión.
Dos eventos son una excepción a la forma anterior. linkage.rejected y linkage.expired llevan únicamente decision: omiten el bloque device que llevan todos los demás eventos y, en un padre kyb, omiten también company y legal_representatives. Son justamente los dos eventos que le dicen a un integrador que un representante está bloqueando a la empresa, así que hay que identificar al representante que bloquea mediante el endpoint de consulta de representantes en lugar de leerlo del payload.
Para onboarding.documents_requested, el bloque data lleva una forma distinta, específica del evento, documentada junto a ese evento.
data.decision
Sección titulada «data.decision»Presente en los eventos que llevan una decisión o una transición de estado de vínculo (onboarding.approved/rejected/review/blocked y linkage.*).
| Campo | Descripción |
|---|---|
result | approved, rejected, pending_review, blocked, o el estado del vínculo (pending_representative, in_progress, completed, expired). |
risk_level | Derivado del score de la decisión, comparado con las bandas de la matriz que decidió el caso. Cuando esas bandas están ausentes o son inutilizables, rige la escala por defecto: low (score hasta 150), medium (hasta 350), high (hasta 700), critical (por encima de 700). Una matriz recalibrada mueve los cortes, así que la etiqueta sigue la configuración que produjo el score, y no la vigente. |
decided_by | Siempre engine en los payloads de webhook. El endpoint de consulta puede informar manual para las decisiones de un analista. |
decided_at | Momento de la decisión, en ISO 8601. |
data de onboarding.threshold_crossed
Sección titulada «data de onboarding.threshold_crossed»Propuesta, versión 1, por la razón indicada en
Cruce de umbral y step-up: contract_version viaja
en el payload para que una ratificación sea una enmienda numerada. El
execution_id y el reference_id del envelope apuntan al recorrido de step-up
que abrió el cruce.
{ "event": "onboarding.threshold_crossed", "execution_id": "5c2f31aa-0000-0000-0000-000000000000", "flow_type": "kyc", "reference_id": "5c2f31aa-0000-0000-0000-000000000000", "timestamp": "2026-09-08T12:03:11Z", "data": { "customer_id": "9b4771d5-ab60-4b5e-867d-573ed7cb684c", "customer_type": "person", "threshold": 1000, "currency": "USD", "step_up": "habitual_block", "classification": "habitual", "first_operation_date": "2026-03-14", "crossed_at": "2026-09-07T21:32:00Z", "journey_url": "https://acme.onp.prod.guardline.com.br/onboarding/link/<token>", "contract_version": 1 }}journey_url viaja en el payload por la misma razón que resume_url viaja en
onboarding.documents_requested: sin él el evento no es accionable, porque el
integrador no tiene otra forma de mandar al cliente al recorrido que se abrió.
Encabezados
Sección titulada «Encabezados»| Encabezado | Descripción |
|---|---|
Content-Type | Siempre application/json. |
X-Guardline-Event-ID | UUID estable del evento, idéntico en todos los intentos del mismo evento y siempre igual al event_id del cuerpo. El encabezado se sigue enviando, pero la firma no lo cubre: deduplique con el event_id del cuerpo firmado y use el encabezado para correlacionar registros. Consulte Deduplicación. |
X-Guardline-Delivery-ID | El identificador de la fila de entrega. Es estable en todos los intentos de esa entrega, no único por intento: un reintento lleva el mismo valor que llevó el primer intento. No deduplique sobre él, o cada reintento se descartará en silencio. |
X-Guardline-Attempt-Number | Número secuencial del intento (1 en la primera entrega, 2 en el primer reintento, etc.). Es el único discriminador por intento. |
X-Guardline-Timestamp | Timestamp Unix del momento de la firma (solo en el modo hmac). |
X-Guardline-Signature | Firma HMAC-SHA256 en hexadecimal, hecha con el secreto vigente (solo en el modo hmac). |
X-Guardline-Signatures | Lista separada por comas con una firma por secreto válido, primero la del secreto vigente (solo en el modo hmac). Fuera de una rotación tiene un único elemento, igual a X-Guardline-Signature. Durante la ventana de una rotación trae también la firma hecha con el secreto anterior. Acepte la entrega si cualquier elemento coincide. Consulte Rotación de credenciales. |
Ocp-Apim-Subscription-Key | Clave de suscripción de la pasarela de API. Se envía en todas las entregas en el modo mtls_apim, donde es obligatoria. |
X-API-Key | Clave estática que espera el endpoint del integrador, cuando hay una configurada en el endpoint del webhook. Opcional e independiente de la firma HMAC. |
Deduplicación
Sección titulada «Deduplicación»Cada evento lleva un UUID propio, event_id, dentro del cuerpo de la entrega, junto a event_sequence. Es el mismo valor que viaja en el encabezado X-Guardline-Event-ID, se mantiene idéntico en todos los reintentos de la misma entrega y nunca se repite entre dos eventos. Como lo que se firma es {timestamp}.{body}, la firma cubre el event_id; el encabezado queda fuera del material firmado, así que una entrega reproducida con el encabezado alterado verificaría igual que la original si el receptor tomara la identidad del evento solo de él.
Claves de deduplicación, en orden de preferencia:
event_iddel cuerpo, autenticado por la firma. Es la clave recomendada en todos los modos de autenticación: el campo se inyecta en el cuerpo en cada entrega, sea cual sea el modo.execution_id+event_sequence, cuando el cuerpo no traeevent_id. El par identifica un evento de forma única: la secuencia se asigna por ejecución bajo un bloqueo, es estable entre los reintentos de la misma entrega y nunca se reutiliza para dos eventos de la misma ejecución.
El encabezado X-Guardline-Event-ID se sigue enviando y siempre es igual al event_id del cuerpo. Sirve para correlacionar registros y depurar; no lo use como única clave de deduplicación.
Las entregas anteriores a la introducción del campo no traen la clave event_id en el cuerpo. El receptor debe tolerar su ausencia y caer en la segunda clave, en lugar de rechazar la entrega.
No deduplique por X-Guardline-Delivery-ID: identifica la fila de entrega, no el evento, y descartaría cada reintento en silencio.
Autenticación del webhook
Sección titulada «Autenticación del webhook»Guardline admite dos modos de autenticación para la entrega de webhooks, hmac y mtls_apim. El modo se configura por endpoint de webhook, y una restricción de la base de datos rechaza cualquier otro valor. No existe ningún modo de entrega con token bearer ni con OAuth: Guardline nunca pone un encabezado Authorization en una solicitud de webhook.
Los dos modos son excluyentes, no acumulativos. hmac firma el cuerpo; mtls_apim autentica en la capa de transporte y no envía ningún encabezado de firma.
| Modo | Firma HMAC | Certificado de cliente | Clave de suscripción |
|---|---|---|---|
hmac (por defecto) | Sí | No | No |
mtls_apim | No | Sí | Sí |
Autenticación HMAC-SHA256
Sección titulada «Autenticación HMAC-SHA256»La autenticación HMAC usa un secreto compartido provisionado durante la configuración del integrador en el panel administrativo.
Proceso de verificación en el receptor:
- Extraiga el encabezado
X-Guardline-Timestamp. - Rechace si el timestamp tiene más de 5 minutos de antigüedad respecto al reloj del servidor.
- Concatene el timestamp y el cuerpo de la solicitud:
{timestamp}.{body}. - Calcule el
HMAC-SHA256de la concatenación usando el secreto compartido. - Codifique el resultado en hexadecimal.
- Compárelo con el encabezado
X-Guardline-Signatureusando una comparación de tiempo constante. - Rechace si hay discrepancia.
El cuerpo firmado incluye el event_id del evento, así que la misma verificación autentica la identidad con la que después se deduplica. El encabezado X-Guardline-Event-ID no forma parte del material firmado.
Pseudocódigo:
import hmac, hashlib, time
def verify_webhook(body: bytes, timestamp: str, signature: str, secret: str) -> bool: if abs(time.time() - int(timestamp)) > 300: return False
message = f"{timestamp}.".encode() + body expected = hmac.new( secret.encode(), message, hashlib.sha256 ).hexdigest()
return hmac.compare_digest(expected, signature)Autenticación mTLS
Sección titulada «Autenticación mTLS»La TLS mutua autentica la entrega en la capa de transporte en lugar de firmar el cuerpo. El modo apunta a una pasarela de API y no a un listener HTTPS simple, y exige el encabezado Ocp-Apim-Subscription-Key descrito más arriba.
La configuración es del lado del operador. Guardline no emite un certificado para que el integrador confíe en él; presenta el que se le haya entregado:
- El operador sube, mediante la API de administración, el certificado de cliente y su clave privada que Guardline debe presentar.
- El operador sube un paquete de CA. Es opcional, y cuando está presente Guardline lo usa para verificar el certificado del servidor de destino. No se distribuye al integrador.
- El operador configura la clave de suscripción de la pasarela, que es obligatoria en este modo.
- En cada entrega, Guardline presenta el certificado de cliente subido y envía la clave de suscripción en
Ocp-Apim-Subscription-Key. - La pasarela valida el certificado contra el almacén de confianza con el que se la haya configurado.
Nada en Guardline establece ni comprueba un CN, así que no condicione la aceptación a ese campo: el certificado lleva el sujeto que haya subido el operador. La identidad del par del lado de Guardline es la verificación ordinaria de nombre de host contra la URL configurada.
Cuando mtls_apim está habilitado, los encabezados X-Guardline-Signature y X-Guardline-Timestamp no se envían.
Configuración del endpoint
Sección titulada «Configuración del endpoint»El endpoint de webhook se configura en el panel administrativo de Guardline. Requisitos:
| Requisito | Detalle |
|---|---|
| Protocolo | Se exige HTTPS. |
| Versión de TLS | 1.2 o superior. |
| Respuesta de éxito | Cualquier código 2xx (el cuerpo se ignora). |
| Redirecciones | No se siguen. Un 301 o un 302 se rechaza. |
| Encabezados de respuesta | Enviar la línea de estado en menos de 5 segundos. |
| Timeout | Completar la respuesta en menos de 10 segundos. |
| Idempotencia | Deduplicar las entregas usando el event_id del cuerpo firmado; si la entrega no lo trae, usar execution_id + event_sequence. Consulte Deduplicación. |
Recomendación de implementación: reciba el webhook, encólelo para su procesamiento asíncrono y responda de inmediato con 200. Esto evita los timeouts y asegura una entrega confiable.
Dos de esas filas les cuestan tiempo a las integraciones, así que conviene enunciarlas con claridad.
El presupuesto de respuesta no es una sola ventana de 10 segundos. El transporte también aplica un timeout de 5 segundos para los encabezados de respuesta, así que un receptor que tarda 7 segundos en enviar su línea de estado falla aunque esté dentro de los 10 segundos documentados. Envíe los encabezados temprano y termine el cuerpo después.
Las redirecciones se rechazan de plano, no se siguen. Un endpoint que está detrás de un 301 o un 302 nunca recibe la entrega. Peor aún, el rechazo se manifiesta como un error de transporte y no como un estado HTTP, así que cuenta como reintentable y consume todo el presupuesto de reintentos antes de que la entrega se agote. Configure la URL final.
Rotación de credenciales
Sección titulada «Rotación de credenciales»Cuando se rotan los certificados mTLS o el secreto HMAC, los reintentos en curso de entregas ya despachadas siguen usando las credenciales que estaban activas en el momento del despacho. Las entregas nuevas usan las credenciales actualizadas de inmediato.
Rotar el secreto HMAC no deja afuera al receptor que todavía no cambió el valor. El secreto anterior sigue siendo válido durante una ventana de superposición, de 24 horas por defecto, y en ella cada entrega lleva en X-Guardline-Signatures una firma con el secreto nuevo y otra con el anterior. X-Guardline-Signature sigue siendo solo la del secreto nuevo. Para cambiar el secreto sin perder entregas, verifique contra todos los elementos de X-Guardline-Signatures, publique el valor nuevo en el receptor dentro de la ventana y recién entonces pida a Guardline que cierre el secreto anterior. Ante una filtración, Guardline puede revocar el anterior en el acto, lo que corta a cualquier receptor que todavía lo use.
Un operador tiene dos vías de reenvío, y no se comportan igual:
| Vía | Credenciales usadas | Fila de entrega |
|---|---|---|
| Reenvío reconstruido a partir de la configuración vigente | La configuración tal como está ahora, así que la rotación se recoge. | Crea una entrega nueva. La original se mantiene intacta a efectos de auditoría. |
| Reencolado de la entrega existente | La instantánea de credenciales capturada cuando la entrega se encoló. | Reutiliza la misma fila. |
La distinción importa después de una rotación: un operador que rota un certificado y luego reencola la entrega existente la reenvía con las credenciales revocadas, y vuelve a fallar por el mismo motivo.
La vía de reconstrucción tiene una consecuencia para el receptor. Como crea una entrega nueva, el reenvío lleva un id de evento nuevo y un número de secuencia nuevo. Eso rompe las dos claves en las que este documento le dice al integrador que se apoye: el event_id (en el cuerpo y en el encabezado X-Guardline-Event-ID) ya no coincide con el original, así que la deduplicación no reconocerá el reenvío como la repetición de un evento ya procesado, y el event_sequence nuevo no coloca el evento donde le corresponde en la cadena de la ejecución.
Política de reintentos
Sección titulada «Política de reintentos»Cuando una entrega falla, Guardline la reintenta con backoff exponencial. El intervalo de abajo es la espera posterior al fallo del intento numerado:
| Intento fallido | Espera antes del intento siguiente |
|---|---|
| 1 | 30 segundos |
| 2 | 2 minutos |
| 3 | 10 minutos |
| 4 | 30 minutos |
| 5 | 1 hora |
| 6 | 2 horas |
| 7 | 4 horas |
| 8 | 8 horas |
| 9 | 12 horas |
| 10 | Ninguna. La entrega se agota. |
Todos los intervalos anteriores son nominales, por dos razones.
Cada retardo programado se multiplica por un factor de jitter aleatorio entre 0,8 y 1,2, así que un intervalo nominal de 1 hora cae en cualquier punto entre 48 y 72 minutos.
El despachador vacía la cola en un tick de 30 segundos y no de forma continua. Una entrega cuyo retardo ya transcurrió igual espera al tick siguiente, y ese mismo tick retrasa la primerísima entrega de cualquier evento hasta 30 segundos después del propio evento. No trate un webhook como una señal en tiempo real.
No todos los fallos se ganan la escalera anterior. Se aplica a los timeouts, a los errores de transporte, a las respuestas 5xx y a exactamente dos códigos 4xx. Cualquier otro 4xx se trata como no reintentable y agota la entrega en el primer intento, sin ningún reintento. Eso cubre el fallo real más probable: un receptor que rechaza el envoltorio, una clave de entrada rotada del lado del integrador o una ruta que todavía no está en servicio. Todos ellos consumen la entrega de inmediato en lugar de dejar una ventana para corregir el problema. Las dos excepciones 4xx que sí se reintentan son 408 Request Timeout y 429 Too Many Requests.
Tras 10 intentos fallidos, la entrega se marca como exhausted y se genera una alerta interna. Las notificaciones siguen accesibles mediante el endpoint de polling (consulte Polling de notificaciones). El reenvío manual también está disponible en el panel administrativo.
Polling de notificaciones
Sección titulada «Polling de notificaciones»El endpoint de polling es el mecanismo de respaldo para asegurar que no se pierda ninguna notificación, con independencia de los fallos de los webhooks. También pueden usarlo como mecanismo de integración principal los integradores que prefieren un modelo pull.
El polling lee las mismas entregas que envía el webhook, así que hereda la suscripción de eventos de la configuración. Una configuración de webhook puede suscribirse solo a algunos eventos; un evento fuera de la lista se descarta antes de convertirse en entrega, y entonces no se envía por webhook ni aparece acá. Una configuración sin lista se suscribe a todos los eventos, que es como se comporta toda configuración anterior a esta función. Si usa el polling como red de seguridad, confirme con Guardline que la configuración se suscribe a todos los eventos que usted procesa.
Consulta de notificaciones pendientes
Sección titulada «Consulta de notificaciones pendientes»GET /api/v1/onboarding/notificationsEste endpoint usa paginación basada en cursor.
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
status | string | pending | Filtra por estado: pending, retrying, exhausted. Separados por comas para varios valores. |
limit | integer | 50 | Elementos por página (de 1 a 200; los valores fuera del rango recaen en 50). |
after_cursor | string | (ninguno) | Cursor de la página siguiente: el delivery_id codificado en base64 que la llamada anterior devolvió en next_cursor. Lea la advertencia sobre paginación de más abajo antes de usarlo. |
El filtro acepta failed, pero el despachador nunca asigna ese estado, así que pedirlo no devuelve nada. El estado terminal que un integrador realmente necesita es exhausted, que sí funciona.
La paginación por cursor solo es confiable para la primera página. La consulta ordena las filas por fecha de creación descendente, pero compara el cursor contra la clave primaria, que es un UUID aleatorio. Los dos ordenamientos no tienen nada que ver entre sí, así que toda página posterior a la primera es un subconjunto arbitrario que a la vez omite filas y las repite. No pagine en profundidad. Obtenga la primera página, confirme el procesamiento de lo que procesó y vuelva a obtener la primera página: las filas confirmadas desaparecen del resultado, así que la cola se vacía sin cursor.
Respuesta (200):
{ "error": false, "data": { "items": [ { "delivery_id": "d4e5f6a7-b8c9-0123-def0-123456789abc", "execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "event": "onboarding.approved", "flow_type": "kyc_minor", "reference_id": "SOL-2026-00042", "event_sequence": 6, "status": "pending", "attempts": 3, "timestamp": "2026-04-01T10:45:00Z" } ], "next_cursor": null }}next_cursor es una cadena base64 cuando pueden existir más páginas (se devuelve cuando la página está llena), o null cuando no hay más elementos. Devuélvalo como after_cursor para obtener la página siguiente, con la advertencia anterior en mente. Cada elemento lleva flow_type y reference_id, el recuento actual de attempts y el timestamp de la entrega.
attempts se incrementa por adelantado en cada fallo, así que va uno por delante de los intentos realmente realizados: una fila que muestra 3 se intentó dos veces.
last_attempt_at no se devuelve. El campo se obtiene de la marca de tiempo de entrega, que solo se escribe cuando hay éxito, y este endpoint devuelve únicamente filas no entregadas, así que está estructuralmente siempre ausente. No escriba un consumidor que dependa de él.
El listado no está acotado a la configuración de webhooks del integrador. En una instancia que además ejecuta la salida de gestión de casos, las entregas dirigidas a ese receptor interno aparecen en el mismo listado, con nombres de evento fuera del catálogo documentado aquí. Filtre por los nombres de evento que reconozca e ignore el resto.
Confirmación del procesamiento
Sección titulada «Confirmación del procesamiento»POST /api/v1/onboarding/notifications/{delivery_id}/ackMarca una notificación como procesada. Tras la confirmación, la notificación deja de aparecer en las consultas con estado pending. La operación es idempotente: las llamadas repetidas para el mismo delivery_id devuelven éxito y no tienen efectos secundarios.
Respuesta (200):
{ "error": false, "data": { "delivery_id": "d4e5f6a7-b8c9-0123-def0-123456789abc", "status": "acknowledged" }}Diagramas de flujo completos
Sección titulada «Diagramas de flujo completos»KYC estándar
Sección titulada «KYC estándar»KYC Minor con notificación entregada por Guardline
Sección titulada «KYC Minor con notificación entregada por Guardline»Referencia de errores
Sección titulada «Referencia de errores»Códigos de estado HTTP
Sección titulada «Códigos de estado HTTP»| Código | Significado | Orientación |
|---|---|---|
| 200 | Éxito | Procesar normalmente. |
| 201 | Recurso creado | Procesar normalmente. |
| 400 | Solicitud inválida | Corrija el payload. No reintente. |
| 401 | Fallo de autenticación | Verifique la API Key (o el JWT en resend-representative-link). No reintente. |
| 403 | Prohibido | La credencial se verificó pero no lleva ningún rol autorizado (JWT) ni el alcance exigido (API key). No reintente. |
| 404 | Recurso no encontrado | Verifique los identificadores. No reintente. |
| 405 | Método no permitido | La ruta existe pero no para este verbo. Corrija la llamada. No reintente. |
| 409 | Conflicto | Trátelo según el código de error específico. |
| 410 | Gone | El recurso era válido pero ya no es accesible (por ejemplo, un enlace expirado). |
| 413 | Payload demasiado grande | El cuerpo superó el tope global de 29 MB o el tope de la ruta. No reintente. |
| 422 | No procesable | El cuerpo se parseó pero viola una regla de dominio. Corrija la solicitud. No reintente. |
| 429 | Límite de solicitudes superado | Dos limitadores distintos responden 429; ver más abajo. |
| 500 | Error interno | Reintente con backoff exponencial (máximo 3 intentos). |
| 502 | Error interno durante el procesamiento | Reintente con backoff exponencial. |
El 429 hay que dividirlo, porque solo una de sus dos fuentes se puede esperar. Los limitadores por IP (resolución del enlace, dispensador, representantes KYB) envían un encabezado Retry-After y se liberan solos: espere el intervalo indicado y reintente. El tope de reenvíos de POST .../resend-representative-link es un contador vitalicio sobre la ejecución, no una ventana. No envía Retry-After, nunca se reinicia y reintentar no puede tener éxito nunca. Lea ese 429 como “esta ejecución se quedó sin reenvíos” y deténgase; consulte Límite.
Códigos de error de dominio
Sección titulada «Códigos de error de dominio»Nota: los campos
messagede error se localizan segúnAccept-Language(consulte Idioma de los mensajes de error). Haga la ramificación programática sobrecode.
| Código | HTTP | Descripción |
|---|---|---|
MISSING_TOKEN | 401 | Ninguna credencial llegó al handler: no se envió ni X-API-Key ni Authorization. Este es el 401 habitual en todos los endpoints autenticados, incluidos los de clave de API. También cubre la falta del JWT en resend-representative-link y la ausencia del token del dispensador en la ruta (400 en esa ruta). |
INVALID_API_KEY | 401 | API Key inválida, revocada o expirada. |
INVALID_TOKEN | 401 | Se envió un JWT bearer y falló la verificación: firma incorrecta, emisor incorrecto o expirado. |
FORBIDDEN | 403 | El JWT carece del rol requerido (admin o supervisor), o la API key declara alcances y ninguno es el que exige la ruta. En ese segundo caso el mensaje termina con los alcances que alcanzarían. Consulte Alcances de la clave. |
FLOW_ID_REQUIRED | 400 | flow_type sin flow_id en una instancia con más de un flujo activo de ese tipo. |
INVALID_REQUEST | 400 | JSON malformado, campo desconocido o tipo de campo incorrecto. |
INVALID_ID | 400 | Un UUID de la ruta está malformado. |
VALIDATION_FAILED | 400 | Falló la validación a nivel de campo, incluida una solicitud que no envía ni flow_id ni flow_type. |
INVALID_TAX_ID | 400 | primary.tax_id no coincide con la forma que emite el país del flujo resuelto. |
INVALID_IDEMPOTENCY_KEY | 400 | Encabezado Idempotency-Key de más de 255 caracteres. |
LINKED_SUBJECTS_REQUIRED | 400 | Solicitud kyc_minor con linked_subjects ausente, vacío o sobredimensionado, o con un rol incorrecto. |
INVALID_FLOW_TYPE | 400 / 422 | 400 cuando flow_id se resuelve a un tipo de flujo de uso interno; 422 cuando un tipo de flujo no admite un dispensador de QR reutilizable. |
MISSING_REQUIRED_FIELDS | 400 | kyc_minor sin primary.tax_id o sin primary.full_name, o bloque operation sin direction o sin wallet_type. |
PHONE_POLICY_VIOLATION | 422 | Un flujo mobile-only recibió un número de línea fija. |
CUIT_NOT_JURIDICAL_PERSON | 422 | company.tax_id no es un CUIT de persona jurídica (prefijo 30, 33 o 34). |
COMPANY_DOCUMENT_INVALID | 422 | El documento de la empresa no coincide con el documento que declara el flujo. Es el hermano de CUIT_NOT_JURIDICAL_PERSON para flujos que no usan CUIT, como el RUT uruguayo, y existe para no indicarle al solicitante que empiece con 30, 33 o 34 en un flujo donde esa regla no existe. |
COMPANY_TAX_ID_LOCKED | 409 | El CUIT de la empresa se suministró al crear la sesión y no se puede editar dentro del recorrido. Lo lanza el envío del paso del recorrido, PUT /api/v1/executions/{execution_id}/steps/{step_id}, y no ningún endpoint del Resumen de endpoints. |
UNPROCESSABLE | 422 | Se violó una política de URL, como un redirect_url que no es una URL https absoluta. |
CPF_MINOR_REPRESENTATIVE_MATCH | 422 | El CPF del representante es igual al CPF del menor. |
LINKED_SUBJECT_RELATIONSHIP_REQUIRED | 422 | Una entrada kyc_minor omitió linked_subjects[0].subject.relationship. |
REPRESENTATIVE_AGE_BELOW_MINIMUM | 422 | El birth_date del representante indica una edad menor de 18 años. |
ACTIVE_SESSION_EXISTS | 409 | Ya existe una sesión activa para el mismo sujeto primario y el mismo flujo, al crear una sesión o cuando el step-up de un cruce de umbral la encuentra. |
CUSTOMER_ALREADY_REGISTERED | 409 | El flujo bloquea el alta nueva y el documento pertenece a un legajo cuya alta ya terminó. No lleva resume_url. Ver Política de cliente ya registrado. |
CUSTOMER_NOT_FOUND | 404 | Ningún legajo activo para el id indicado, en las rutas de sesión por id de cliente. Distinto de FLOW_NOT_FOUND, porque los dos pueden faltar en una misma solicitud. |
CUSTOMER_FLOW_TYPE_MISMATCH | 422 | Un id de persona en un flujo de empresa, o un id de empresa en un flujo de persona. |
ALREADY_EXISTS | 409 | Creación de un legajo cuyo documento ya pertenece a uno activo, o idempotency_key de cruce de umbral ya usada. |
DUPLICATE | 409 | Una restricción de unicidad rechazó la escritura, normalmente dos llamadas concurrentes compitiendo por crear la misma fila. |
EXECUTION_NOT_FOUND | 404 | Ejecución no encontrada. La lectura no lleva ningún predicado de tenant, así que esto es una comprobación de existencia y no una prueba de propiedad; el aislamiento es la base de datos por instancia (consulte Entornos). |
NOT_FOUND | 404 | Fallo genérico. La cancelación devuelve este código, no EXECUTION_NOT_FOUND, cuando el id de la ejecución no corresponde a nada. |
EXECUTION_TERMINAL | 409 | Se solicitó una cancelación sobre una ejecución que ya estaba en estado terminal. |
EXECUTION_TERMINATED | 409 | La ejecución primaria alcanzó un estado terminal (resend-representative-link). |
NO_RESENDABLE_CHILD | 404 | No hay ningún hijo representante para reenviar (solo en resend-representative-link). |
LINK_NOT_FOUND | 404 | El token del enlace no existe. En las rutas del dispensador, también un dispensador desactivado o un id de enlace desconocido. |
LINK_EXPIRED | 410 | El token del enlace expiró, o un dispensador activo fue detenido por un tope (expires_at o max_sessions). |
CONFLICT | 409 | Rotación o cambio de límites de un dispensador ya desactivado. |
FLOW_NOT_FOUND | 404 | Flujo no encontrado. |
RATE_LIMITED | 429 | Se superó el límite de solicitudes. |
PAYLOAD_TOO_LARGE | 413 | El cuerpo de la solicitud superó el tope global de 29 MB, o el tope de la propia ruta. Las rutas de documento corporativo aceptan hasta 28 MB de cuerpo, lo suficiente para un archivo de 20 MiB después de la expansión de base64; las demás rutas de documento y de biometría siguen en 4 MB. |
ROUTE_NOT_FOUND | 404 | La ruta no coincide con ninguna ruta registrada. |
METHOD_NOT_ALLOWED | 405 | La ruta existe pero no acepta este verbo. |
INTERNAL_ERROR | 500 / 502 | El respaldo genérico para un fallo sin mapear, lanzado en la mayoría de los endpoints y no solo en los de representantes KYB. 500 desde el manejador de errores compartido; 502 donde un handler envuelve una llamada aguas abajo, como hacen los endpoints de representantes KYB y de sesiones. |
Resumen de endpoints
Sección titulada «Resumen de endpoints»| Método | Ruta | Autenticación | Descripción |
|---|---|---|---|
| GET | /api/v1/onboarding/flows | API Key o JWT | Lista los recorridos disponibles. |
| GET | /api/v1/onboarding/requirements | API Key o JWT | Lista lo que hay que recolectar, por tipo de cliente y clasificación. Está en el mismo grupo que /flows y toma la misma credencial; la aplicación cliente lo llama antes de iniciar una sesión. |
| POST | /api/v1/onboarding/sessions | API Key o JWT | Crea una sesión de onboarding (con sujetos vinculados, prellenado de empresa y URL de retorno opcionales). |
| GET | /api/v1/link/{token} | Público (con límite de solicitudes) | Resuelve un token de enlace al payload de flujo y cliente. Sanitizado cuando la sesión se creó con surface_hint=embed. |
| GET | /api/v1/onboarding/executions/{execution_id} | API Key o JWT | Consulta una ejecución con los datos completos. |
| GET | /api/v1/onboarding/executions | API Key o JWT | Lista las ejecuciones activas. |
| GET | /api/v1/onboarding/executions/{execution_id}/documents | API Key o JWT | Lista todos los documentos del caso: los de la empresa, los de los representantes y los de las personas declaradas. |
| GET | /api/v1/onboarding/executions/{execution_id}/documents/{document_id}/content | API Key o JWT | Descarga un documento del caso, con ETag para solicitud condicional. |
| GET | /api/v1/onboarding/customers/person | API Key o JWT | Lista los legajos de persona humana, con búsqueda por documento, nombre o e-mail. |
| GET | /api/v1/onboarding/customers/person/{id} | API Key o JWT | Lee el legajo de una persona humana. |
| POST | /api/v1/onboarding/customers/person | API Key o JWT | Crea un legajo de persona humana sin recorrido. Solo document es obligatorio. |
| PUT | /api/v1/onboarding/customers/person/{id} | API Key o JWT | Edita los datos de contacto y declarativos. El documento no es editable. |
| DELETE | /api/v1/onboarding/customers/person/{id} | API Key o JWT | Da de baja el legajo de forma lógica. |
| POST | /api/v1/onboarding/customers/person/{id}/addresses | API Key o JWT | Registra un domicilio. |
| GET | /api/v1/onboarding/customers/company | API Key o JWT | Lista los legajos de empresa. Solo lectura. |
| GET | /api/v1/onboarding/customers/company/{id} | API Key o JWT | Lee el legajo de una empresa. Solo lectura. |
| POST | /api/v1/onboarding/customers/person/{id}/sessions | API Key o JWT | Abre un recorrido para una persona que ya tiene legajo, direccionada por id. |
| POST | /api/v1/onboarding/customers/company/{id}/sessions | API Key o JWT | Abre un recorrido para una empresa que ya tiene legajo, direccionada por id. |
| POST | /api/v1/onboarding/customers/person/{id}/thresholds | API Key o JWT | Reporta el cruce de un escalón del acumulado anual y abre el recorrido de step-up. Contrato propuesto. |
| POST | /api/v1/onboarding/customers/company/{id}/thresholds | API Key o JWT | El mismo reporte, para una empresa. Contrato propuesto. |
| POST | /api/v1/onboarding/executions/{execution_id}/representatives | Ninguna (con límite de solicitudes); consulte Autenticación y límites | Declara los 1..N representantes legales de una empresa KYB y emite sus enlaces de KYC. |
| GET | /api/v1/onboarding/executions/{execution_id}/representatives | Ninguna (con límite de solicitudes); consulte Autenticación y límites | Consulta el estado de KYC en vivo de los representantes declarados. |
| POST | /api/v1/onboarding/executions/{execution_id}/resend-representative-link | JWT de admin o supervisor | Reenvía el enlace del representante por los canales de notificación del flujo. |
| POST | /api/v1/onboarding/executions/{execution_id}/accounts | API Key | Registra una cuenta que el core abrió a partir de la ejecución. |
| GET | /api/v1/onboarding/executions/{execution_id}/accounts | API Key o JWT | Lista las cuentas de la ejecución con su historial de transiciones. |
| POST | /api/v1/onboarding/executions/{execution_id}/accounts/{account_id}/bancarization | API Key | Confirma la bancarización y vuelve operativa la cuenta. |
| GET | /api/openapi.json | API Key | Especificación OpenAPI servida en el host de la instancia. |
| POST | /api/v1/onboarding/executions/{execution_id}/cancel | API Key o JWT | Igual que la siguiente; la ruta recomendada para integradores. |
| POST | /api/v1/executions/{execution_id}/cancel | API Key o JWT | Cancela una ejecución (y su hijo representante en kyc_minor), liberando el hueco de sesión activa para su reutilización. |
| POST | /api/v1/onboarding/dispensers | API Key o JWT de Admin/Supervisor/Analista | Crea u obtiene el dispensador de QR reutilizable de un flujo. |
| POST | /api/v1/onboarding/dispensers/{token}/sessions | Público (con límite de solicitudes) | Emite una sesión nueva a partir de la lectura de un QR. |
| GET | /api/v1/onboarding/dispensers?flow_id={flow_id} | API Key o JWT de Admin/Supervisor/Analista | Lista los enlaces reutilizables de un flujo, activos o no, con estado derivado y topes. |
| POST | /api/v1/onboarding/dispensers/{id}/deactivate | API Key o JWT de Admin/Supervisor | Desactiva un enlace reutilizable. Idempotente. |
| POST | /api/v1/onboarding/dispensers/{id}/rotate | API Key o JWT de Admin/Supervisor | Reemplaza un enlace reutilizable por uno con token nuevo, en la misma transacción. |
| PUT | /api/v1/onboarding/dispensers/{id}/limits | API Key o JWT de Admin/Supervisor | Define o quita los topes max_sessions y expires_at de un enlace activo. |
| GET | /api/v1/onboarding/notifications | API Key o JWT | Consulta las notificaciones de webhook pendientes. |
| POST | /api/v1/onboarding/notifications/{delivery_id}/ack | API Key o JWT | Confirma una notificación de webhook. |
Las dos rutas de representantes están registradas sin ningún middleware de autenticación. Una credencial enviada a ellas nunca se lee, así que una clave de API revocada se acepta exactamente igual que una ausente; el límite de solicitudes es la única barrera. Consulte Autenticación y límites.
Versión 18, actualizada el