Pular para o conteúdo
Esta página descreve a API ONP da linha brasileira da plataforma. A versão em espanhol descreve a linha de Argentina e Uruguai.

Referência da API ONP

Este documento descreve como integrar com a API de onboarding da Guardline. Ele cobre autenticação, criação de sessões de onboarding, declaração de sujeitos vinculados (o representante legal para kyc_minor na criação da sessão), resolução dos links de sessão, acompanhamento do ciclo de vida da execução, recebimento de notificações via webhook e polling de eventos perdidos.

O modelo de integração segue uma separação clara de responsabilidades: a Guardline verifica e decide, o integrador orquestra. O integrador declara o onboarding com uma única chamada de API, distribui as URLs retornadas na resposta (ou pede que a Guardline as entregue) e recebe notificações de progresso e de decisão final via webhook.

O fluxo de integração envolve quatro participantes:

ParticipanteResponsabilidade
Backend do integradorCria sessões, recebe webhooks, consulta execuções
API da GuardlineGerencia sessões, tokens, execuções, sujeitos vinculados e notificações
Frontend do integradorRenderiza o link diretamente ou monta um iframe apontando para ele
Onboarding GuardlineConduz a jornada de verificação com o cliente final

Uma única chamada POST /api/v1/onboarding/sessions cria um onboarding para qualquer tipo de fluxo. A requisição carrega os dados do sujeito principal mais um array opcional de sujeitos vinculados. A resposta carrega uma URL por sujeito criado. A notificação dos sujeitos vinculados é opt-in por chamada e sai pelos canais configurados para o fluxo (SMS e e-mail por padrão, WhatsApp quando solicitado).

Para kyc_minor, o integrador declara na mesma requisição tanto o menor de idade (em primary) quanto o representante legal (em linked_subjects[]). A jornada de cada um é independente: o menor abre a sua URL, o representante abre a dele, e a Guardline orquestra a consolidação. O integrador decide se a Guardline entrega a URL do representante pelos canais de notificação do fluxo (notify.linked: true) ou se a entrega fica a cargo do integrador (notify.linked: false ou omitido).

Todas as chamadas de API seguem o padrão REST sobre HTTPS, com payloads JSON e respostas no formato de envelope padrão da Guardline.

AmbienteURL basePrefixo da API key
Sandboxhttps://{instance}.onp.dev.guardline.com.brgl_test_
Produçãohttps://{instance}.onp.prod.guardline.com.brgl_live_

O valor {instance} é provisionado durante a configuração do integrador e identifica a infraestrutura dedicada. Cada instância tem banco de dados, aplicação e armazenamento próprios e isolados.

Todas as chamadas de API voltadas ao integrador exigem a chave correspondente ao ambiente. Dois formatos de header são aceitos de forma equivalente, X-API-Key e Authorization: Bearer. Envie um dos dois:

X-API-Key: gl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
Authorization: Bearer gl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

As chaves são provisionadas pelo painel administrativo da Guardline. O token em texto claro é exibido apenas no momento da criação. Internamente, a Guardline armazena o hash SHA-256 da chave.

O prefixo é um rótulo escolhido na criação da chave, não um mecanismo de roteamento, e assume live por padrão quando o ambiente não é especificado. Uma chave emitida em uma instância de sandbox sem ambiente explícito aparece, portanto, como gl_live_ mesmo sendo uma chave exclusiva de sandbox. O prefixo não diz nada sobre qual instância vai aceitá-la: uma chave autentica apenas na instância que a emitiu.

Isso vale para X-API-Key, cujo valor é sempre lido como uma API key. Em Authorization: Bearer, o prefixo de fato roteia: a credencial só é tratada como API key quando começa com gl_live_ ou gl_test_, e qualquer outro valor nesse header é verificado como um JWT. Uma chave sem prefixo enviada como Bearer, portanto, falha na verificação do JWT e responde 401 INVALID_TOKEN. Envie uma chave assim em X-API-Key.

Um problema de credencial sempre responde 401, nunca 400. MISSING_TOKEN quando nenhum header foi enviado, INVALID_API_KEY quando a chave é desconhecida, revogada ou expirada. Um 400 significa que a chave foi aceita e o corpo da requisição foi rejeitado, portanto reemitir a chave não resolve.

Requisições HTTP são rejeitadas. HTTPS com TLS 1.2 ou superior é obrigatório em todos os ambientes.

Duas superfícies fogem dessa regra:

EndpointAutenticação
POST /api/v1/onboarding/executions/{execution_id}/resend-representative-linkJWT de admin ou supervisor do backoffice da Guardline. Consulte Reenvio do link do representante.
GET /api/v1/link/{token}Público. A posse do token é a credencial de acesso, e a rota tem limite de requisições por IP de origem.

Endpoints que alteram estado aceitam o header de requisição opcional Idempotency-Key (qualquer string única de no máximo 255 caracteres, normalmente um UUID; uma maior retorna 400 INVALID_IDEMPOTENCY_KEY). Quando presente, o servidor guarda a resposta em cache por 15 minutos; chamadas seguintes com a mesma chave retornam a resposta em cache com o header de resposta Idempotency-Replayed: true. Sem o header, cada chamada é processada de forma independente.

O cache é indexado pelo par (chave, endpoint), portanto reutilizar uma mesma chave em dois endpoints diferentes não gera colisão.

Use o header ao repetir uma chamada após um timeout de rede, para evitar execuções duplicadas. Gere uma chave nova para cada nova operação lógica.

Todas as respostas seguem o envelope padrão:

Sucesso:

{
"error": false,
"data": {}
}

Erro:

{
"error": true,
"message": "descrição do erro",
"code": "ERROR_CODE",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}

O campo message é localizado. Os integradores devem ramificar a lógica pelo campo code (estável e não traduzido), não pelo texto da mensagem.

O campo request_id é retornado apenas nas respostas de erro e pode ser usado para rastreamento em chamados de suporte. Uma resposta de sucesso carrega error e data, mais um array opcional warnings com entradas {code, message, context} que reporta degradações não fatais sem transformar a resposta em um erro. warnings é omitido quando não há nada a reportar.

O campo message é resolvido a partir do header de requisição Accept-Language. Quem não envia nenhum header recebe o idioma padrão da instância (pt-BR, salvo se a instância estiver configurada de outra forma).

Accept-LanguageResultado
ausente, não interpretável ou sem correspondênciao idioma padrão da instância (pt-BR, salvo se a instância estiver configurada de outra forma)
pt-BRpt-BR
es-AR, es sem região, e as variantes latino-americanas como es-UYEspanhol (Argentina)
Terminal window
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"
}

O catálogo é indexado pelo code do erro. Um código sem entrada no idioma solicitado cai para a sua mensagem em pt-BR, e não para um texto em branco ou um placeholder, de modo que a resposta nunca fica vazia e o code é sempre a referência autoritativa.

Por isso, os integradores nunca devem comparar com o texto da mensagem, nem montar a partir dele um texto exibido ao cliente final: ramifique a lógica pelo code e renderize o seu próprio texto.

GET /api/v1/onboarding/flows

Retorna a lista de jornadas configuradas e ativas do integrador, com os metadados necessários para criar sessões corretamente.

kyc_representative e kyb_representative são filtrados no servidor e não aparecem: esses filhos são criados apenas como entradas de linked_subjects[] (representative em uma sessão kyc_minor, officer em uma sessão kyb). O fluxo de KYC dos beneficiários finais de uma empresa kyb também não aparece, porque só a própria plataforma o abre. Apontar o flow_id para qualquer um deles é rejeitado com 400 INVALID_FLOW_TYPE. Selecione as jornadas pelo campo type, e não pela posição, e trate kyc, kyb e kyc_minor como o conjunto que pode ser iniciado.

{
"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.",
"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.",
"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 fluxo também retorna flow_id (a versão específica do fluxo, utilizável como flow_id em POST /sessions) e steps_count. O steps_count é lido da coluna do próprio registro do fluxo, e não contado a partir do array steps da mesma resposta, portanto os dois podem divergir. Quando o número precisa estar correto, use o tamanho de steps.

O array primary_schema descreve os campos aceitos no bloco primary de POST /api/v1/onboarding/sessions. Para kyc_minor, o integrador declara ainda o representante legal em linked_subjects[] (consulte Sujeitos vinculados). A jornada do representante ocorre de forma independente da jornada do menor; o steps acima lista apenas as etapas do menor.

POST /api/v1/onboarding/sessions

Cria uma nova sessão de onboarding e retorna uma URL por sujeito. Para kyc_minor, a resposta inclui uma URL separada para o representante legal vinculado, além da URL do menor de idade.

CampoTipoObrigatórioDescrição
flow_typestringUm entre flow_type e flow_idTipo de jornada. Valores possíveis: kyc, kyb, kyc_minor. Só resolve quando a instância tem um único fluxo ativo desse tipo; com mais de um, responde 400 FLOW_ID_REQUIRED. Prefira sempre flow_id.
flow_idUUIDUm entre flow_type e flow_idVersão específica do fluxo. Use este campo para fixar uma configuração de fluxo específica quando existirem vários fluxos do mesmo tipo. A resolução para um fluxo cujo tipo é de uso interno (como kyc_representative) é rejeitada com INVALID_FLOW_TYPE.
channelenumNãoOrigem da sessão: web, sdk, api. Padrão: web. O alias legado channel_code também é aceito; quando ambos são enviados, channel prevalece.
layout_modeenumNãoLayout da interface da jornada: stepper ou chat. Não há padrão do lado da API: quando o campo é omitido, a jornada é renderizada com o layout configurado no próprio fluxo.
reference_idstring (≤ 255)NãoCorrelacionador de formato livre para o sistema do integrador. Retornado nos webhooks e consultável via API. Recomendado: um identificador único e estável, como o ID da conta ou o ID da solicitação de onboarding.
parent_execution_idUUIDNãoVincula esta execução a uma execução pai. Reservado para casos de uso avançados, como subfluxos de KYB; não é necessário para jornadas kyc/kyc_minor/kyb padrão.
metadataobjectNãoRepasse de formato livre. Armazenado junto com a sessão e não devolvido por nenhum endpoint de leitura, portanto é somente escrita do lado do integrador. Use reference_id para um correlacionador que precise ser lido de volta.
surface_hintenumNãoDefina como embed quando o integrador for renderizar a jornada dentro de um iframe. Controla a sanitização de PII em GET /api/v1/link/{token}. Consulte Surface hint (modo incorporado).
primaryobjectObrigatório para kyc_minor; opcional para kyc e kybDados do sujeito principal. Os campos são descritos pelo primary_schema retornado em GET /flows. Pode ser omitido (cold-start) para kyc e kyb; nesse caso, o registro do cliente é materializado quando o usuário envia a etapa de documento na jornada. primary.tax_id é validado conforme o tipo do fluxo resolvido; consulte Identificação fiscal do sujeito principal.
linked_subjectsarrayObrigatório para kyc_minor (exatamente uma entrada, role: "representative"); opcional nos demais tipos de fluxo (entradas role: "officer", usadas no kyb)Sujeitos vinculados ao principal. Consulte Sujeitos vinculados.
notifyobjectNãoFlags de opt-in de notificação. Consulte Opt-in de notificação.
biometricbooleanNãoLiga ou desliga a etapa de prova de vida facial apenas nesta sessão, sobrepondo a configuração da jornada: false suprime a etapa (a pessoa avança direto para a seguinte), true a habilita em uma jornada que a traz desligada, e a ausência mantém a configuração da jornada. Exige que o fluxo resolvido tenha a etapa de biometria (400 VALIDATION_FAILED caso contrário, nos dois sentidos). Vale apenas para a execução principal; as jornadas dos sujeitos vinculados mantêm a configuração do fluxo delas. O webhook terminal reflete a escolha em data.liveness.status (consulte data.liveness).
partnerobjectNãoIdentifica quem opera a jornada pelo portal do próprio integrador, como o atendente de uma loja. O único campo é partner.email (até 254 caracteres), obrigatório quando o bloco é enviado: espaços nas pontas são removidos, e um bloco vazio ou um endereço inválido retorna 400 VALIDATION_FAILED. Não cria cliente nem execução vinculada. O endereço é guardado com a sessão; em instâncias com os e-mails de status ao parceiro habilitados, os e-mails de status da jornada vão para ele em vez de para o cliente, e uma sessão criada sem partner não os envia a ninguém. É também o destino dos avisos de correção cadastral (consulte Correção cadastral).

primary.tax_id é validado contra o tipo do fluxo resolvido, e não contra o flow_type bruto da requisição, portanto uma requisição que envia apenas flow_id passa pela mesma verificação.

Tipo do fluxoAceito em primary.tax_id
kyc, kyc_minorUm CPF válido: 11 dígitos, com os dígitos verificadores conferidos.
kybUm CNPJ válido de 14 caracteres, numérico ou alfanumérico (IN RFB 2.229/2024), com os dígitos verificadores conferidos.

A entrada formatada é aceita. O servidor remove a máscara antes da verificação (no CNPJ, também converte as letras para maiúsculas) e grava a forma limpa, portanto 123.456.789-09 e 12345678909 são o mesmo documento e encontram o mesmo cliente. Uma divergência retorna 400 INVALID_TAX_ID.

O array linked_subjects declara qualquer sujeito vinculado ao principal pelo contrato do fluxo. Hoje, isso cobre o representante legal do kyc_minor (representative) e os representantes legais de uma empresa kyb (officer).

Cada entrada tem:

CampoTipoObrigatórioDescrição
roleenumSimDiscriminador de papel: representative ou officer. O kyc_minor exige exatamente uma entrada com representative. officer declara um representante legal de uma empresa kyb, sem limite de quantidade: cada entrada cria uma execução filha no subfluxo de representante configurado no fluxo pai, e uma entrada com tax_id e full_name cria ou reutiliza o cadastro da pessoa. Um fluxo pai sem subfluxo de representante recusa a entrada com 400 VALIDATION_FAILED, e qualquer outro valor de role também retorna 400 VALIDATION_FAILED.
subjectobjectSimBloco de dados do sujeito. Pode ser vazio ({}) para criação shell.

O bloco subject aceita os seguintes campos:

CampoTipoObrigatórioDescrição
full_namestringPareado com tax_id (veja Obrigatoriedade dos campos abaixo)Nome completo.
tax_idstringPareado com full_nameDocumento de identificação da pessoa. Este campo nunca é validado: qualquer string é aceita e a sessão é criada mesmo assim, com 201. Cabe a quem chama enviá-lo normalizado, apenas com dígitos; um valor formatado cria um cliente duplicado indexado pelo texto formatado.
phonestring (E.164)NãoTelefone com código do país. Não é exigido por notify.linked: o disparo precisa de phone ou email, e só é interrompido quando os dois estão ausentes. Validado contra a política de telefone do fluxo: um fluxo mobile-only rejeita telefones fixos com 422 PHONE_POLICY_VIOLATION.
birth_datestring (YYYY-MM-DD)NãoData de nascimento. Quando informada, deve indicar 18 anos ou mais; um representante legal mais novo é rejeitado com 422 REPRESENTATIVE_AGE_BELOW_MINIMUM.
emailstringNãoE-mail.
relationshipenumObrigatório para kyc_minor; opcional caso contrárioRelação do representante legal com o menor. Obrigatório na criação da sessão para kyc_minor, independentemente de o restante de subject ser um shell. Valores: mother, father, grandparent, legal_guardian, tutor, other. Consulte Cardinalidade de kyc_minor.
notification_channelenumNãoCanal preferencial para a entrega gerenciada pelo backend: sms, whatsapp ou email_sms.

Obrigatoriedade dos campos de linked_subjects[i].subject

Seção intitulada “Obrigatoriedade dos campos de linked_subjects[i].subject”

O serviço aceita três formatos:

FormatoConteúdoEfeito
Shellsubject sem tax_id e full_name ao mesmo tempo (em kyc_minor, ele ainda carrega relationship)Cria a execução vinculada sem materializar um registro de cliente. O integrador entrega a URL manualmente.
Mistosubject: {tax_id: "..."} ou subject: {full_name: "..."} (apenas um dos dois)Aceito; tratado como shell. O campo isolado não dispara a materialização do cliente. Não é um erro.
Materializadosubject: {tax_id: "...", full_name: "...", ...} (ambos presentes)Cria ou reutiliza um registro de cliente por tax_id e o vincula de volta à execução vinculada. Os demais campos do sujeito (email, phone, birth_date) preenchem o novo registro de cliente na criação; são ignorados quando o cliente já existe (consulte Semântica de reutilização do CPF).

Independentemente do formato, notify.linked: true precisa de pelo menos um campo de contato no sujeito: phone, email ou os dois. Cada canal é ignorado quando o campo de destinatário correspondente está vazio, e o disparo só é interrompido antes de qualquer chamada ao provedor quando os dois estão ausentes; nesse caso, notify_sent é registrado como false. Um representante legal que só tem e-mail é notificado pela via de e-mail.

Para flow_type: kyc_minor, subject.relationship é obrigatório na criação da sessão, seja qual for o formato acima. Um shell (subject: {}) sem relationship é rejeitado; consulte Cardinalidade de kyc_minor.

Para flow_type: kyc_minor, o serviço exige exatamente uma entrada em linked_subjects com role: "representative". Qualquer um dos casos a seguir retorna 400 LINKED_SUBJECTS_REQUIRED:

  • Campo linked_subjects ausente
  • linked_subjects: [] (array vazio)
  • Mais de uma entrada
  • Uma entrada com role diferente de representative

A entrada também precisa carregar subject.relationship. A ausência de subject.relationship retorna 422 LINKED_SUBJECT_RELATIONSHIP_REQUIRED. A relação é a fonte canônica do tipo de responsável consumido adiante pela trava do documento de vínculo e pelo salto automático da etapa de relação dentro da jornada, por isso precisa ser declarada logo no início, mesmo quando o restante do sujeito é um shell.

A criação shell continua explícita e possível; basta incluir relationship: envie linked_subjects: [{role: "representative", subject: {relationship: "mother"}}].

Para kyc_minor, duas validações adicionais se aplicam à entrada do representante legal:

  • linked_subjects[0].subject.relationship é obrigatório. Omiti-lo retorna 422 LINKED_SUBJECT_RELATIONSHIP_REQUIRED.
  • O tax_id do representante legal deve ser diferente do tax_id do menor de idade. Reutilizar o mesmo CPF retorna 422 CPF_MINOR_REPRESENTATIVE_MATCH.

O bloco notify controla se a própria Guardline entrega a URL, por SMS, e-mail ou WhatsApp.

CampoTipoPadrãoDescrição
notify.primaryboolfalseReservado. Atualmente não tem efeito; versões futuras vão disparar a URL do sujeito principal.
notify.linkedboolfalseQuando true, a Guardline registra o opt-in e dispara a URL do representante legal quando o menor de idade conclui o módulo dele (não na criação da sessão). O plano padrão é SMS mais e-mail; o WhatsApp só é usado quando subject.notification_channel o indica pelo nome. O opt-in se aplica ao representante do kyc_minor e precisa de subject.phone, subject.email ou os dois.

Para kyc_minor, o representante legal só é necessário depois que o menor de idade conclui a jornada. A Guardline persiste o opt-in de notify.linked no POST /sessions e dispara a mensagem nesse momento posterior (quando a execução pai passa para pending_representative), e não de forma síncrona durante a criação da sessão. Como resultado, o campo linked_subjects[i].notify_sent na resposta do POST /sessions é sempre false. O resultado real do disparo aparece depois em representative_notify_sent no GET /api/v1/link/{token} do token da execução pai (lido do registro da execução filha).

Quando o disparo ocorre, os canais são selecionados por:

  1. linked_subjects[i].subject.notification_channel, quando presente: sms envia apenas SMS, whatsapp envia apenas WhatsApp, email_sms envia SMS e e-mail.
  2. Caso contrário, vale o plano padrão, que é o mesmo conjunto de email_sms: SMS e e-mail. O WhatsApp nunca faz parte do fallback e só é disparado quando é pedido pelo nome.

Cada via é então ignorada quando o campo de destinatário correspondente está vazio: SMS e WhatsApp precisam de subject.phone, e o e-mail precisa de subject.email.

O campo linked_subjects[i].notify_sent na resposta do POST /sessions é sempre false, porque o disparo é adiado para a conclusão do menor de idade (consulte Momento do disparo). Ele não indica se uma mensagem foi enviada.

Para observar o resultado real do disparo, leia representative_notify_sent em GET /api/v1/link/{token}:

  • true: pelo menos um canal tentado foi aceito pelo seu provedor. Para SMS e WhatsApp, isso significa que o provedor de mensageria retornou um identificador de mensagem; para e-mail, significa que o provedor de e-mail aceitou a mensagem para entrega.
  • false: notify.linked estava desligado, o sujeito não tinha nem phone nem email, ou todos os canais tentados foram recusados.

Um ID de mensagem do provedor não significa que a mensagem foi entregue ao destinatário. A confirmação assíncrona de entrega (callbacks de status do provedor) não é exposta nesta API; ela permanece nos registros de log de comunicação acessíveis pelo painel administrativo. Essa é uma escolha deliberada para manter a superfície da API honesta e enxuta.

Os integradores que renderizam a jornada dentro de um iframe definem surface_hint: "embed" na requisição. O backend persiste a flag na execução. Quando o iframe carrega /api/v1/link/{token} para renderizar a jornada, a resposta é sanitizada (CPF, email, phone e birth_date removidos do bloco do cliente; campos de URL do representante removidos do payload da execução pai). Consulte Comportamento da sanitização de PII.

O valor de surface_hint é interno ao servidor e não é devolvido em nenhuma resposta (nem em /sessions, nem em /link/{token}). Os integradores observam o efeito dele apenas pelo formato sanitizado do bloco customer em /link/{token}.

O endpoint legado GET /api/v1/embed/{token}, que funcionava como um resolvedor sanitizado separado, foi removido. Os integradores em modo incorporado agora usam /api/v1/link/{token} depois de criar a sessão com surface_hint: "embed".

Quando primary.tax_id corresponde a um registro ativo existente em customers_person, o serviço reutiliza o cliente existente em vez de criar um novo. Nesse caso, os campos primary.full_name, primary.email, primary.phone e primary.birth_date da requisição são ignorados; o cliente mantém os dados da primeira criação.

O mesmo vale para linked_subjects[i].subject.tax_id quando o CPF do sujeito vinculado corresponde a um registro existente.

Os dois campos não são tratados da mesma forma. primary.tax_id é normalizado antes da busca (consulte Identificação fiscal do sujeito principal), portanto a grafia não importa. linked_subjects[i].subject.tax_id não é validado nem normalizado: a correspondência é feita sobre a string exata enviada, então duas grafias do mesmo documento são duas chaves diferentes e produzem dois registros de cliente diferentes. Envie esse campo apenas com dígitos e sempre da mesma forma.

Motivo: idempotência. Repetir a chamada com o mesmo CPF não duplica registros de cliente. Contrapartida: os dados do cliente ficam presos à primeira criação; correções feitas em chamadas posteriores de POST /sessions não se propagam.

Para atualizar o nome, o e-mail ou o telefone de um cliente existente, use o backoffice ou os endpoints de gestão de clientes; POST /sessions não é o caminho para atualizações de cadastro. No desenvolvimento local, use um CPF novo a cada teste ou apague o registro existente antes de reutilizar o CPF.

KYC padrão, cold-start (sem dados do sujeito principal)

Seção intitulada “KYC padrão, cold-start (sem dados do sujeito principal)”
{
"flow_type": "kyc",
"channel": "web",
"reference_id": "SOL-2026-00099"
}

O registro do cliente é materializado quando o usuário envia a etapa de documento na jornada. Até lá, customer_id fica ausente da resposta.

{
"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 menor de idade com dados completos e notificação entregue pela Guardline

Seção intitulada “KYC menor de idade com dados completos e notificação entregue pela 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 }
}

A Guardline cria as duas execuções e registra o opt-in de notify.linked. O SMS para +5511999998888 contendo a URL do representante legal é disparado quando o menor de idade conclui o módulo dele, e não na criação da sessão; por isso, a resposta do POST /sessions retorna notify_sent: false (consulte Momento do disparo).

Seção intitulada “KYC menor de idade com representante shell (o integrador entrega o link)”
{
"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" } }
]
}

A URL do representante legal é retornada na resposta (linked_subjects[0].url). O integrador a entrega pelo próprio canal. Nenhuma notificação é disparada (notify.linked assumiu o padrão false). O campo relationship é obrigatório no kyc_minor mesmo no formato shell; consulte Cardinalidade de kyc_minor.

{
"flow_type": "kyc",
"surface_hint": "embed",
"primary": {
"full_name": "Lucas Almeida",
"tax_id": "12345678909"
}
}

A flag surface_hint da sessão controla a sanitização de PII em GET /api/v1/link/{token} quando o iframe resolve o token.

{
"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
}
]
}
}
CampoDescrição
execution_idIdentificador único da execução do sujeito principal.
customer_idIdentificador do registro do cliente. Ausente da resposta em cold-start (sem dados de primary enviados), porque ainda não existe registro de cliente; presente e estável assim que o registro existe. Teste a presença da chave, e não a igualdade com o UUID zerado: versões anteriores publicavam 00000000-0000-0000-0000-000000000000 aqui e os integradores liam esse valor como um identificador que seria preenchido depois, e é por isso que agora o campo é omitido.
urlURL pública da jornada do sujeito principal.
embed_urlURL que aponta para a rota compatível com iframe do sujeito principal. Use este valor como src do iframe ao renderizar dentro da aplicação do integrador.
tokenSlug do link, também aceito por GET /api/v1/link/{token}.
expires_atExpiração do link em ISO 8601. Momento da criação mais uma configuração válida para todo o deploy, 7 dias por padrão. Não é configurável por fluxo, portanto todos os links que uma instância emite carregam a mesma janela.
statusStatus inicial da execução (created).
reference_idEco do reference_id enviado na requisição, quando informado.
linked_subjects[]Uma entrada por sujeito vinculado criado. Sempre presente (array vazio quando a requisição não declarou sujeitos vinculados).
linked_subjects[i].rolePapel do sujeito vinculado (representative no kyc_minor, officer no kyb).
linked_subjects[i].execution_idID da execução do sujeito vinculado.
linked_subjects[i].urlURL da jornada do sujeito vinculado.
linked_subjects[i].embed_urlURL compatível com iframe do sujeito vinculado.
linked_subjects[i].subject_statusStatus inicial do sujeito vinculado (created).
linked_subjects[i].notify_sentSempre false nesta resposta. O disparo para o representante legal é adiado para a conclusão do menor de idade, portanto o resultado real é lido depois em representative_notify_sent no GET /api/v1/link/{token} (consulte Momento do disparo).

O campo url é destinado ao acesso direto do usuário por SMS, WhatsApp, e-mail ou qualquer outro canal escolhido pelo integrador. O campo embed_url é destinado ao uso em iframe e webview. Ambos apontam para a mesma execução e resolvem para o mesmo conteúdo.

Nota: os campos message de erro são localizados por Accept-Language. Faça a correspondência por code para ramificações programáticas, não pelo texto da mensagem.

HTTPCódigoCausa
400INVALID_REQUESTJSON malformado, campo desconhecido (a mensagem nomeia o campo) ou tipo de campo incorreto.
400INVALID_IDEMPOTENCY_KEYO header Idempotency-Key tem mais de 255 caracteres.
400VALIDATION_FAILEDFalha de validação em nível de campo: nem flow_id nem flow_type foi enviado, flow_id não é um UUID, flow_id e flow_type divergem, o fluxo resolvido está inativo, channel fora do enum, relationship ou notification_channel fora do enum, birth_date do kyc_minor fora da faixa de 14 a 17 anos, linked_subjects[i].role inválido, entrada officer em um fluxo sem subfluxo de representante, biometric enviado para um fluxo sem a etapa de biometria, ou partner vazio ou com partner.email inválido. O envelope não indica qual dessas condições ocorreu; correlacione com o request_id.
400INVALID_TAX_IDprimary.tax_id não é um CPF válido em um fluxo kyc ou kyc_minor, nem um CNPJ válido em um fluxo kyb. Consulte Identificação fiscal do sujeito principal.
400FLOW_ID_REQUIREDA requisição enviou flow_type sem flow_id, e a instância tem mais de um fluxo ativo desse tipo. Envie o flow_id lido em GET /flows.
400LINKED_SUBJECTS_REQUIREDRequisição kyc_minor com o array linked_subjects ausente, vazio ou acima do tamanho permitido, ou com um papel diferente de representative.
400INVALID_FLOW_TYPEO flow_type não é um entre kyc, kyb, kyc_minor, ou o flow_id resolve para um tipo de fluxo de uso interno (como kyc_representative).
400MISSING_REQUIRED_FIELDSRequisição kyc_minor sem primary.tax_id ou sem primary.full_name.
422LINKED_SUBJECT_RELATIONSHIP_REQUIREDRequisição kyc_minor cujo linked_subjects[0].subject.relationship está ausente ou vazio. Vale para todos os formatos de kyc_minor (shell, misto, materializado).
401MISSING_TOKENNem X-API-Key nem Authorization foi enviado, ou a credencial estava vazia.
401INVALID_API_KEYAPI Key inválida, revogada ou expirada.
401INVALID_TOKENApenas para chamadas com JWT: a credencial bearer não carrega o prefixo gl_live_/gl_test_, portanto é verificada como um JWT, e essa verificação falhou.
403FORBIDDENCom JWT: o token não carrega nenhum papel autorizado, ou a conta está desativada.
404FLOW_NOT_FOUNDO flow_id não existe.
409ACTIVE_SESSION_EXISTSJá existe uma sessão ativa para o mesmo sujeito principal e o mesmo fluxo. O corpo também carrega execution_id e resume_url da sessão que bloqueou esta, para que quem chama possa devolver a pessoa a ela em vez de criar outra. resume_url só está presente enquanto o link daquela sessão continua válido.
422PHONE_POLICY_VIOLATIONUm fluxo mobile-only recebeu um número de telefone fixo em primary.phone ou linked_subjects[i].subject.phone.
422CPF_MINOR_REPRESENTATIVE_MATCHRequisição kyc_minor em que o tax_id do representante legal é igual ao tax_id do menor de idade.
422REPRESENTATIVE_AGE_BELOW_MINIMUMO birth_date do representante legal indica idade inferior a 18 anos.
422LINKED_SUBJECT_RELATIONSHIP_REQUIREDRequisição kyc_minor cuja entrada do representante legal omite subject.relationship.
500INTERNAL_ERROREntre outras causas: o flow_type nomeia um tipo sem fluxo ativo para o integrador. A consulta retorna um erro puro de nenhuma linha encontrada que nada mapeia para um 4xx, portanto uma requisição que na verdade é um erro do cliente aparece como erro de servidor. Repetir não adianta; corrija o flow_type ou envie flow_id.
GET /api/v1/link/{token}

Endpoint público (sem autenticação). O frontend do integrador, o iframe ou qualquer navegador que abra a url da resposta da sessão chama este endpoint para carregar a jornada. Retorna a definição do fluxo, as etapas e o bloco customer.

Quando a sessão foi criada com surface_hint: "embed", a resposta é sanitizada (consulte Comportamento da sanitização de PII).

Limite de requisições: o limite é compartilhado por todo o namespace /api/v1/link/* (não por token) e é modulado pelo perfil de ambiente em que a API roda. O perfil padrão permite 10 requisições por hora por IP de origem; o perfil de desenvolvimento permite 120 requisições por minuto. O sandbox é servido a partir de um host de desenvolvimento, portanto o sandbox tem 120 por minuto e a produção tem 10 por hora. Só as resoluções que falham (resposta 400 ou acima) contam para o limite: resoluções bem-sucedidas não consomem o bucket, portanto vários links válidos resolvidos a partir do mesmo IP não o esgotam. Depois que um IP enche o bucket com falhas, porém, até um link válido vindo dele é recusado até a janela virar.

Resolver um token não o consome nem o rotaciona. O mesmo token continua podendo ser resolvido de novo até que um relógio dispare (a expiração do link ou a janela de sessão ativa descrita em Erros, abaixo).

{
"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",
"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
}
}
CampoDescrição
execution_idIdentificador da execução do sujeito ao qual este token pertence.
flowDefinição do fluxo: id, type, name, layout_mode, cpf_match_mode (alert ou block), phone_policy (any ou mobile-only), settings e os steps ordenados. Cada etapa carrega id, execution_step_id, step_type_code, step_type_name, step_order, title, description, config, is_required, is_enabled, status e (apenas na superfície padrão) display_data e completed_fields.
customerCadastro do cliente. Campos abaixo.
customer.idUUID do cadastro do cliente. Em cold-start, e em toda execução KYB, é o UUID zero 00000000-0000-0000-0000-000000000000, com cpf_masked vazio. Observe a assimetria com POST /sessions, que omite customer_id na mesma situação: este payload sempre carrega o bloco customer, portanto a ausência de um cadastro aparece como zeros, e não como uma chave ausente. Verifique o UUID zero aqui e a presença da chave lá.
customer.nameNome completo. Vazio em um registro cold-start.
customer.cpfCPF sem máscara. Removido quando surface_hint=embed.
customer.cpf_maskedCPF mascarado: três asteriscos seguidos dos quatro últimos dígitos (***8909). A chave está sempre presente, nunca é omitida, e não depende de cpf: ela carrega a string vazia quando não há cadastro de cliente a mascarar, o que acontece em cold-start e em toda execução KYB, e sobrevive a surface_hint=embed mesmo que o próprio cpf seja removido ali.
customer.emailE-mail. Removido quando surface_hint=embed.
customer.phoneTelefone em E.164. Removido quando surface_hint=embed.
customer.birth_dateData de nascimento em YYYY-MM-DD. Removido quando surface_hint=embed.
minorQuando este token pertence ao representante legal de um par kyc_minor, carrega o bloco de cliente do menor de idade (mesmo formato de customer); null caso contrário. Definido como null quando surface_hint=embed. A chave nunca é removida, portanto o payload carrega "minor": null em vez de omiti-la.
relationshipRelação do representante legal com o menor de idade (mother, father, etc.). Presente apenas em tokens kyc_minor; omitido caso contrário.
statusStatus atual da execução. Consulte Ciclo de vida da execução.
expires_atExpiração do link.
allowed_originsOrigens autorizadas a incorporar o iframe (CSP frame-ancestors).
representative_linkURL da jornada de um filho vinculado. Presente sempre que esta execução não tem pai e tem pelo menos um filho, sem nenhuma verificação do tipo de fluxo: o resolvedor pega o filho criado mais recentemente na execução, qualquer que seja o papel dele. Removido quando surface_hint=embed.
representative_tokenO token de link bruto desse mesmo filho (o FE usa para redirecionamento). Mesma condição de presença de representative_link. Removido quando surface_hint=embed.
representative_statusStatus atual da execução desse filho, copiado literalmente do código de status do filho, portanto carrega um estado de execução de Ciclo de vida da execução e nada mais. Um representante legal recém-provisionado aparece como created, e não como pending: pending não faz parte do vocabulário de status e este campo nunca pode carregá-lo. Preservado sob surface_hint=embed para que o FE saiba se deve fazer polling.
representative_notify_senttrue quando o disparo para o representante legal (executado na conclusão do menor de idade, consulte Momento do disparo) retornou um ID de mensagem do provedor; false quando notify.linked estava desligado ou nenhum canal estava acessível; ausente quando a execução tem pai ou não tem nenhum filho. Removido quando surface_hint=embed.

Atenção: em um pai KYB com representantes declarados (officer), os campos representative_* expõem um deles. Os quatro dependem de uma única condição, a de que a execução não tenha pai, e a consulta por trás deles pega o filho criado mais recentemente, sem filtro de tipo de fluxo. A única exclusão são as jornadas dos beneficiários finais, que nunca saem por aqui: o link de um beneficiário vai apenas para o e-mail dele. Portanto, resolver o link público de um pai KYB retorna o representative_link de um representante legal declarado e o representative_token bruto dele, que é uma credencial válida para a jornada daquela pessoa. Quando uma empresa declara vários representantes, o payload nomeia exatamente um deles, o mais recente, e qual deles é pode mudar conforme novos representantes são adicionados. Trate o token de link de um pai KYB como algo que também alcança a jornada do representante.

Quando a sessão foi criada com surface_hint: "embed", o resolvedor aplica sanitização em processo antes de retornar o payload:

  • Removidos: customer.cpf, customer.email, customer.phone, customer.birth_date, minor (definido como null), representative_link, representative_token, representative_notify_sent, todos os flow.steps[].display_data e flow.steps[].completed_fields.
  • Preservados: customer.id, customer.name, customer.cpf_masked, todo o bloco flow (id, type, name, layout_mode, cpf_match_mode, phone_policy, settings, steps), relationship, status, expires_at, allowed_origins, representative_status, execution_id.

Sessões criadas sem surface_hint (padrão) recebem o payload completo.

Nota: os campos message de erro são localizados por Accept-Language. Consulte Idioma das mensagens de erro.

HTTPCódigoCausa
404LINK_NOT_FOUNDO token não existe.
410LINK_EXPIREDDois gatilhos, e o segundo dispara enquanto expires_at ainda está no futuro. Um: expires_at já passou. Dois: a execução está em in_progress e a janela de sessão ativa dela, contada a partir da primeira resolução do link, se esgotou. A janela de sessão só é aplicada em in_progress, portanto uma execução parada em created ou pending_representative nunca é recusada por ela e continua podendo ser resolvida até expires_at.
429RATE_LIMITEDLimite do perfil de ambiente excedido pelas resoluções que falharam, compartilhado entre todos os tokens: 10 por hora por IP de origem no perfil padrão, 120 por minuto no perfil de desenvolvimento em que o sandbox roda.
POST /api/v1/onboarding/executions/{execution_id}/resend-representative-link

Refaz o disparo de notificação para o representante legal de uma execução kyc_minor existente. Não cria uma nova execução de representante; reutiliza o filho vinculado existente e tenta a notificação novamente.

Este endpoint exige um JWT de admin ou supervisor obtido no backoffice da Guardline. O header X-API-Key sozinho não é suficiente. Chamadas sem um JWT válido retornam 401 MISSING_TOKEN.

O endpoint é destinado a fluxos administrativos de recuperação (a equipe de suporte do integrador detecta que o representante legal não recebeu o link e o operador clica em “reenviar” no backoffice). Não foi projetado para ser chamado por automações do integrador. Para disparos conduzidos pelo integrador, defina notify.linked: true no POST /sessions original e (se o link expirar) crie uma nova sessão.

O endpoint respeita o header Idempotency-Key; consulte Idempotência.

No máximo 3 reenvios por execução. A 4ª tentativa retorna 429 RATE_LIMITED.

O limite é um contador vitalício na execução, e não uma taxa dentro de uma janela de tempo. Ele não é reiniciado: uma vez registrados 3 reenvios, essa execução nunca mais pode ser reenviada, e esperar não adianta. Leia o 429 como “esta execução não tem mais reenvios”, e não como “tente mais tarde”.

Um reenvio que falha também consome um dos três. O contador é incrementado antes de o filho ser resolvido e antes de o disparo ser tentado, de propósito, para que um integrador não consiga ultrapassar o limite provocando erros. Uma chamada que responde 404 NO_RESENDABLE_CHILD ou que falha durante o disparo já queimou um slot, portanto um operador que esbarre nesses casos pode acabar com menos de três reenvios utilizáveis.

HTTPCódigoCausa
400INVALID_IDO execution_id do path não é um UUID válido.
400VALIDATION_FAILEDA chamada foi feita no tipo errado de execução: o fluxo não é kyc_minor (uma execução KYB ou KYC comum cai aqui, e não em um 404), ou a execução não tem representante legal vinculado.
401MISSING_TOKENJWT não informado.
403FORBIDDENO JWT não tem o papel admin ou supervisor.
404EXECUTION_NOT_FOUNDA execução principal não existe.
404NO_RESENDABLE_CHILDNão há filho representante em estado não terminal para reenvio. O slot de reenvio já foi gasto quando isso ocorre.
409EXECUTION_TERMINATEDA execução principal está em approved, rejected, cancelled ou expired. blocked não é verificado aqui.
429RATE_LIMITEDLimite vitalício de 3 reenvios atingido. Não é reiniciado.
502INTERNAL_ERRORQualquer outra falha: leitura da execução ou do fluxo, leitura ou incremento do contador, ou o próprio disparo da notificação.

A verificação de estado terminal por trás do 409 é mais estreita do que a tabela do ciclo de vida. O reenvio recusa apenas approved, rejected, cancelled e expired; ele não trata blocked como terminal, embora Ciclo de vida da execução marque blocked como terminal. Um reenvio em uma execução blocked, portanto, passa pela barreira do 409 e prossegue.

Vale enunciar explicitamente duas regras que as transições acima codificam. O expires_at só se aplica enquanto a execução está em created ou in_progress: a consulta de expiração não seleciona nenhum outro status, e ela é a única coisa que grava expired em qualquer lugar. Um pai kyc_minor parado em pending_representative, portanto, nunca expira por esse relógio, por mais que espere; quando um filho expira, o worker emite um evento de vínculo no pai e deixa o status do próprio pai intacto. Uma empresa kyb que aguarda os beneficiários finais é a exceção: ela expira quando as rodadas de verificação deles se esgotam (consulte Beneficiários finais). E o estado created termina no envio da primeira etapa, e não na abertura do link: resolver o link apenas registra o início da sessão, portanto um usuário que abre o link e o abandona permanece em created.

EstadoTerminalDescrição
createdNãoSessão criada, nenhuma etapa enviada ainda. Abrir o link não faz sair deste estado; o envio da primeira etapa faz.
in_progressNãoO usuário está navegando pelas etapas da jornada.
pending_representativeNãoAguardando um ou mais representantes legais vinculados. Alcançado de duas formas: um pai kyc_minor cujo menor de idade concluiu a sua jornada, e uma empresa kyb que aguarda a verificação dos seus beneficiários finais (status_detail awaiting_beneficial_owners; consulte Beneficiários finais). Não interprete o estado como “o sujeito concluiu”.
processingNãoTodas as etapas enviadas; motor de decisão processando.
pending_reviewNãoEncaminhado para revisão manual. Não há prazo de resolução no contrato da API: um worker interno abre uma escalação em um caso que ficou aqui além de um limiar configurável, o que direciona atenção em vez de encerrar o caso.
approvedSimAprovado pelo motor ou por um analista.
rejectedSimReprovado pelo motor ou por um analista.
completedSimTodas as etapas concluídas sem decisão própria da execução, que é como um filho vinculado se encerra quando o veredito fica no pai: o representante legal do kyc_minor é o caso que os integradores encontram. Ele não carrega bloco decision. Consulte a execução filha vinculada, como este documento recomenda para kyc_minor, e este é o status que você recebe de volta.
blockedSimJornada interrompida por uma regra de segurança (restrição de CPF, limite de tentativas de biometria).
cancelledSimCancelado pelo integrador ou por um administrador.
expiredSimSessão expirada antes da conclusão.

Para kyc_minor, a execução pai mantém o estado do ciclo de vida acima, enquanto o progresso do representante legal é acompanhado separadamente na execução filha vinculada. GET /api/v1/link/{token} no token do pai expõe o status do representante pelo campo representative_status.

O pai transita para pending_representative quando o menor de idade conclui sua jornada. Enquanto o representante legal cumpre sua parte, o pai permanece em pending_representative; a execução filha percorre created → in_progress → processing → terminal de forma independente. Quando o filho alcança um estado terminal positivo, a consolidação do pai é executada e o pai passa por processing até seu próprio estado terminal.

Toda execução carrega status_detail ao lado de status, na consulta e na listagem. Ele não cria nenhum status novo: um integrador que ignora o campo lê a execução exatamente como antes.

statusstatus_detailSignificado
pending_reviewin_analysisEm análise, sem correção cadastral aberta.
pending_reviewawaiting_correctionUma rodada de correção cadastral está aberta, aguardando o parceiro. Consulte Correção cadastral.
pending_reviewcorrection_submittedO parceiro enviou a correção, que aguarda a análise.
pending_reviewcorrection_expiredO prazo da rodada passou sem envio, e a execução aguarda a análise.
pending_representativeawaiting_beneficial_ownersUma empresa kyb aguarda a verificação dos seus beneficiários finais. Consulte Beneficiários finais.
pending_representativepending_representativeOs demais casos, como o pai kyc_minor que aguarda o representante legal.
qualquer outroo próprio statusRepete o status.

Fora de pending_review e de pending_representative, o subestado é ignorado, portanto um subestado antigo nunca contradiz um status final.

Circula a ideia de que o link vale 24 horas, ou de que vale 7 dias. São três contadores distintos, cada um governando um status diferente, e o primeiro que vencer expira a execução.

RelógioOrigemPadrãoSó expira quando o status é
link_expires_atLINK_EXPIRATION_DAYS da instalação7 diascreated
expires_atexpires_in_hours do próprio fluxo168 h, ou seja 7 diascreated ou in_progress
SessãoSESSION_TIMEOUT_HOURS da instalação, contado a partir de started_at24 hin_progress

O que cada um significa na prática:

  • link_expires_at cobre o link que nunca foi aberto. Como só age sobre created, abrir o link uma vez já tira a execução do alcance desse relógio.
  • expires_at é o prazo da execução inteira, contado desde a criação e derivado do fluxo, não de variável de ambiente. É o único que continua valendo depois que a pessoa começou a responder.
  • O contador de sessão começa quando a pessoa abre o link pela primeira vez, e é ele que fecha a jornada abandonada no meio. É o relógio de 24 horas, e ele não é o prazo do link.

Consequência para a mensagem que o integrador mostra ao seu usuário: quem abandonou a jornada no meio perdeu a sessão de 24 horas, não o link de 7 dias, e são coisas diferentes de se dizer.

O primeiro é publicado na consulta da execução, como expires_at, então o integrador não precisa recalculá-lo.

GET /api/v1/onboarding/executions/{execution_id}

Retorna o estado completo de uma execução, incluindo os dados coletados durante a jornada. Este endpoint é a principal fonte de dados para o integrador. Os webhooks notificam as transições de estado; os detalhes são obtidos por meio desta consulta.

{
"error": false,
"data": {
"execution_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"flow_type": "kyc",
"reference_id": "SOL-2026-00099",
"status": "approved",
"status_detail": "approved",
"decision": {
"result": "approved",
"risk_level": "low",
"decided_at": "2026-03-26T15:30:00Z",
"decided_by": "engine",
"reasons": []
},
"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"
},
"correction": null,
"steps_completed": 8,
"steps_total": 8,
"created_at": "2026-03-26T14:00:00Z",
"updated_at": "2026-03-26T14:12:00Z",
"started_at": "2026-03-26T14:05:00Z",
"completed_at": "2026-03-26T14:12:00Z",
"expires_at": "2026-04-02T14:00:00Z"
}
}
CampoDescrição
execution_idIdentificador da execução.
statusStatus atual da execução. Consulte Ciclo de vida da execução.
status_detailDetalhe do status, sempre presente. Consulte Detalhe do status.
flow_typeTipo de jornada.
reference_idEco do correlacionador do integrador, quando a sessão declarou um.
steps_completed / steps_totalProgresso da jornada.
decisionBloco de decisão: result, risk_level, risk_score, decided_by, decided_at e reasons (consulte Motivos da decisão). Ausente até que exista uma decisão. risk_score é o score inteiro do qual risk_level deriva, omitido quando a execução não tem score (e então risk_level publica low). O result é normalizado a partir do código de decisão interno, e apenas os quatro códigos terminais são mapeados (auto_approved e manual_approved para approved, auto_rejected e manual_rejected para rejected); qualquer outro código é retornado como está. pending_review é, ele próprio, um código de decisão, portanto uma execução em revisão publica "result": "pending_review". Um código que ramifica apenas entre approved e rejected trata errado todos os casos em revisão.
customerPessoa que é o sujeito da execução. Campos abaixo.
data_divergencesSempre presente como um array, vazio quando não há nenhuma divergência. Cada entrada reporta um campo cujo valor enviado durante a jornada divergiu do valor pré-preenchido: field, expected_masked, submitted_masked, step_id, recorded_at. Os valores são mascarados na inserção; os valores brutos nunca saem do backend. field é um entre name, email, phone, birth_date, cpf e identity_document: fluxos fora do Brasil registram a divergência do documento como identity_document, e fluxos brasileiros e linhas antigas continuam usando cpf. Trate os dois.
correctionA rodada mais recente de correção cadastral, ou null quando a execução nunca teve uma. Consulte Correção cadastral.
beneficial_ownersPresente apenas em uma empresa kyb cujo fluxo tem a cadeia de beneficiários finais habilitada. Consulte Beneficiários finais.
created_at / started_at / completed_atTimestamps da jornada.
updated_atA última alteração da execução ou da sua rodada de correção mais recente, a que for posterior.
expires_atO prazo da execução, derivado do expires_in_hours do fluxo.
linksO link da jornada da execução, como { url, embed_url }: url é o link a enviar à pessoa e embed_url a sua variante para iframe, no mesmo formato de url e embed_url de POST /sessions, sempre refletindo o link atual da execução. O integrador pode contar com links.url para recuperar o link da jornada (por exemplo, para reenviá-lo) em vez de criar outra sessão. Diferente de POST /sessions, esta resposta não tem url, embed_url ou token no nível de cima: leia o link deste bloco. O bloco é omitido por inteiro quando a execução nunca recebeu um link. Ele é devolvido seja qual for o estado do link; se o link ainda abre depende da expiração dele e do status da execução.
CampoDescrição
full_nameNome completo conforme coletado.
tax_idO documento de identidade da pessoa, bruto e sem formatação. Em jornadas brasileiras, é o CPF.
tax_id_maskedVariante mascarada, mantida por compatibilidade retroativa. Em depreciação suave: prefira tax_id. Integrações novas devem ignorá-lo; as existentes continuam recebendo-o inalterado. Neste endpoint a máscara são três asteriscos seguidos dos últimos quatro caracteres, portanto 11144477735 é publicado como ***7735. Os dois endpoints não mascaram do mesmo jeito. A Listagem de execuções monta o mesmo campo com outra regra, que preserva os dígitos do meio e esconde as pontas, de modo que o valor da listagem expõe mais do documento do que o valor do detalhe. Nunca compare as duas strings mascaradas, e nunca faça parsing de nenhuma delas.
birth_date, nationality, email, phone, incomeConforme coletados. phone carrega o prefixo de discagem sempre que a origem o carrega, de modo que um número coletado por uma jornada localizada é lido como E.164, e não como dígitos nacionais sem país.
is_pep, has_foreign_tax_obligationSempre emitidos, inclusive quando false.
addresszip_code, street, number, complement, neighborhood, city, state.
document, biometricBlocos de status de verificação: type e status.
representativeDados do representante legal aninhados dentro de customer. Um formato mais restrito do que o bloco acima, e não o mesmo: ele carrega full_name, tax_id_masked, birth_date, nationality, email, phone, income, is_pep, has_foreign_tax_obligation, address, document, biometric, além de relationship, guardianship_document e status. Não há tax_id bruto aqui; o identificador mascarado é a única forma do documento que você recebe. Reservado para compatibilidade retroativa; consulte a nota sobre kyc_minor abaixo.

Em kyc_minor, o bloco customer da execução pai descreve o menor de idade. Os dados do representante legal são obtidos consultando separadamente a execução filha vinculada. Os dados de representative embutidos na resposta da execução pai são reservados para casos de compatibilidade retroativa e podem ser descontinuados em uma versão futura; novas integrações devem consultar explicitamente a execução filha vinculada usando o execution_id retornado em linked_subjects[].execution_id por POST /sessions.

O campo relationship no registro de cliente do representante legal aceita: mother, father, grandparent, legal_guardian, tutor, other. Quando o parentesco é diferente de mother ou father, a jornada do representante legal exige o upload de um documento de guarda; o status do upload é refletido em guardianship_document (null ou "uploaded").

O bloco decision só é preenchido quando a execução chega a um estado terminal (approved, rejected) ou está em revisão (pending_review). Em todos os demais estados, a chave está ausente da resposta, e não presente como null. O mesmo vale para todos os blocos opcionais deste payload (customer, address, document, biometric): teste a presença da chave. A única exceção deliberada é guardianship_document, que é emitido como um null explícito quando nenhum documento foi enviado, e data_divergences, que está sempre presente como um array.

Em instâncias com a correção cadastral habilitada, a análise pode pedir a correção de itens específicos de um cadastro em pending_review. A plataforma abre então uma rodada de correção: uma microjornada separada, que o parceiro preenche dentro do próprio portal. A execução original continua em pending_review durante toda a rodada, e é por ela que o integrador acompanha a correção. A execução da microjornada nunca aparece na listagem, e consultá-la pelo id responde como uma execução inexistente. A habilitação é por ambiente: onde a correção está desligada, correction é sempre null e status_detail em pending_review é sempre in_analysis.

Nenhum webhook acompanha a rodada. O integrador a segue pela leitura da execução original: status_detail muda a cada etapa da rodada, e updated_at avança sempre que a execução ou a sua rodada mais recente mudam, o que permite detectar a mudança por polling.

Nas instâncias que enviam essas notificações, quem foi indicado em partner.email na criação da sessão recebe um e-mail a cada momento da rodada: quando ela é pedida, enviada, retirada, expirada e aceita. O aviso de abertura informa apenas que há uma correção a enviar e o prazo; ele não traz link, itens nem mensagem, porque a correção se abre dentro do portal do parceiro. Sem partner.email, nenhum aviso é enviado, e o cliente nunca os recebe.

O bloco correction traz a rodada mais recente, na consulta e na listagem:

CampoDescrição
idIdentificador da rodada.
roundNúmero da rodada, a partir de 1.
max_roundsO teto de rodadas por execução vigente no momento da leitura. round pode superá-lo depois de o teto ser reduzido; nesse caso, nenhuma rodada nova abre.
stateopen (pedida, o parceiro ainda não a abriu), in_progress (aberta), submitted (enviada, aguardando a análise), accepted, rejected, expired (o prazo passou sem envio), withdrawn (o pedido foi retirado), cancelled ou closed_by_decision (encerrada por uma decisão sobre a execução).
opened_at / due_atQuando a rodada foi pedida e o prazo dela. due_at é o prazo da rodada, e não o expires_at da execução.
submitted_at / closed_atEnvio e encerramento; null enquanto não ocorreram.
close_reasonO próprio estado de encerramento (accepted, rejected, expired, withdrawn, cancelled ou closed_by_decision), ou null com a rodada aberta. Nunca é texto livre.
items_countQuantos itens a rodada pede.
requires_customertrue quando algum item inclui a prova de vida, de modo que o cliente (ou o representante da empresa) precisa estar presente. Só é true quando a jornada tinha a prova de vida ligada ao abrir a rodada.
links.embed_urlO endereço da microjornada, presente apenas enquanto a rodada aceita a entrada do parceiro (open ou in_progress). A microjornada abre somente incorporada no portal do parceiro, por isso não há url direta.

Na consulta individual, o bloco traz ainda:

CampoDescrição
messageA mensagem da análise ao parceiro, apenas quando foi escrita.
itemsOs itens pedidos: item_id, code e label do catálogo aprovado, reason (code, label e guidance), requires_customer, state (pending ou answered) e, em um esclarecimento ou em um documento complementar, question ou document com code e label. Um rótulo ausente do catálogo é publicado como o próprio código.
previous_roundsAs rodadas anteriores, da mais recente para a mais antiga, nos campos da tabela acima sem max_rounds e sem links. Um array vazio na primeira rodada.

Na listagem, cada item traz apenas o resumo, sem message, items e previous_rounds. O bloco nunca carrega a mesa, o analista, uma nota interna ou um motivo interno.

Em um fluxo kyb com a verificação dos beneficiários finais ligada, a empresa não é decidida ao final da própria jornada. A declaração de sócios é congelada, e cada beneficiário final dela (25% ou mais de participação efetiva, somando todos os caminhos pelo CPF) recebe uma jornada de KYC própria, com o link enviado ao e-mail declarado para ele. A empresa passa de processing para pending_representative, com status_detail awaiting_beneficial_owners, e só é decidida quando as verificações terminam; se as rodadas de um beneficiário se esgotam, ela expira. Uma declaração sem nenhum beneficiário final não estaciona: a empresa é decidida na hora.

O bloco beneficial_owners acompanha essa espera na consulta e nas linhas da listagem. Ele aparece em uma empresa kyb (execução raiz) cujo fluxo tem a cadeia de beneficiários habilitada a partir do momento em que a declaração de sócios é congelada, continua presente depois da espera e está ausente em qualquer outra execução. Traz apenas contagens, nunca um nome, um CPF ou um e-mail, sempre sobre a rodada atual de cada beneficiário:

CampoDescrição
totalTodos os beneficiários finais da declaração congelada.
sentOs que ainda não abriram o link.
in_progressOs que estão preenchendo a jornada.
in_reviewOs que estão com o KYC na mesa de análise.
approved / rejectedOs aprovados e os reprovados.
expired / cancelledOs que tiveram o prazo da rodada vencido e os que tiveram a jornada cancelada.
max_roundsQuantas rodadas cada beneficiário tem antes de a empresa expirar; 1 quando a verificação está desligada no fluxo.

Os webhooks dessa espera saem na empresa e estão descritos em Eventos linkage.* (kyc_minor).

O array decision.reasons carrega o motivo canônico por trás da decisão. Ele está sempre presente como array: vazio ([]) nas decisões automáticas do motor e nas aprovações, e preenchido apenas quando uma decisão manual registrou um motivo do catálogo. Hoje ele traz no máximo uma entrada. Cada entrada tem um code legível por máquina e uma description legível por pessoas:

"decision": {
"result": "rejected",
"risk_level": "high",
"decided_at": "2026-03-26T15:30:00Z",
"decided_by": "analyst",
"reasons": [
{ "code": "pep_restriction", "description": "Restrição PEP" }
]
}

Valores possíveis de code:

codedescription
invalid_documentDocumento inválido
fraud_suspectedSuspeita de fraude
pep_restrictionRestrição PEP
sanctions_matchMatch em sanções
biometric_failedBiometria reprovada
high_riskRisco elevado
underageMenor de idade
incomplete_docsDocumentação incompleta
expired_documentDocumento vencido
data_inconsistencyDados inconsistentes
otherOutro

Trate code como o identificador estável para o tratamento programático. A description é o nome do motivo no catálogo da instância, que pode ser ajustado por instância e ampliado com o tempo, portanto tolere códigos desconhecidos. Se o código não estiver no catálogo, a description repete o próprio code.

HTTPCódigoCausa
400INVALID_IDO execution_id do path não é um UUID válido.
401MISSING_TOKENNenhuma credencial foi enviada, nem em X-API-Key nem em Authorization: Bearer.
401INVALID_API_KEYAPI key inválida, revogada ou expirada.
404EXECUTION_NOT_FOUNDNenhuma execução com esse execution_id.
502INTERNAL_ERRORQualquer outra falha ao ler a execução ou o fluxo dela.

O 404 é uma verificação de existência e nada mais. A leitura não carrega nenhum predicado de tenant, portanto ela não consegue informar que um id pertence a outra pessoa; o isolamento aqui é o banco de dados dedicado por instância descrito em Ambientes, e não um teste de propriedade em nível de linha.

GET /api/v1/onboarding/executions

Retorna uma lista paginada de execuções. Por padrão, ficam de fora da resposta as execuções em estado terminal (approved, rejected, blocked, expired, cancelled) e as que ainda estão no estado inicial created (criadas, mas sem nenhuma etapa enviada); informe um status explícito, com um ou mais códigos, para incluí-las (por exemplo, status=created retorna as linhas excluídas nesse estado). A exclusão vale com qualquer outro filtro, inclusive reference_id, tax_id e o período de criação: só um status explícito a desliga.

O filtro é a exclusão por status e nada mais: não há nenhuma verificação separada de se o link ou a sessão da linha continua válido, portanto uma linha cujo link expirou, mas cujo status ainda não foi encerrado, continua sendo retornada.

Os resultados são ordenados por created_at decrescente, com execution_id decrescente como critério de desempate determinístico entre linhas criadas no mesmo instante.

ParâmetroTipoPadrãoDescrição
pageinteiro1Página solicitada.
per_pageinteiro20Itens por página (máximo: 100). Valores acima de 100 são limitados a 100.
statuslista(nenhum)Um ou mais códigos de status, separados por vírgula (status=approved,rejected) ou com o parâmetro repetido (status=approved&status=rejected); um único código continua aceito. Os códigos aceitos são created, in_progress, processing, pending_review, pending_representative, approved, rejected, blocked, completed, cancelled, expired. Quando omitido, ficam de fora approved, rejected, blocked, expired, cancelled e created; qualquer status explícito, de um código ou de uma lista, desliga essa exclusão e retorna as linhas nos status pedidos, inclusive os seis. Qualquer outro valor, inclusive um vazio entre vírgulas, retorna 400 INVALID_REQUEST.
created_fromdata (AAAA-MM-DD)(nenhum)Início do período de criação (created_at), inclusivo, no horário de Brasília (UTC-3): conta a partir das 00:00 desse dia. Pode vir sem created_to.
created_todata (AAAA-MM-DD)(nenhum)Fim do período de criação, inclusivo, no horário de Brasília: conta até o fim desse dia. Pode vir sem created_from. Um created_from posterior a created_to retorna 400 INVALID_REQUEST.
reference_idstring (≤ 255)(nenhum)Correspondência exata com o reference_id do integrador. A exclusão padrão continua valendo: sem status, uma execução em um dos seis estados excluídos não aparece, mesmo com o reference_id exato. Para ler uma execução cujo status você não sabe, consulte-a pelo id em Consulta de uma execução.
flow_idUUID(nenhum)Filtra por uma versão específica do fluxo. Um UUID malformado retorna 400 INVALID_REQUEST.
flow_typestring(nenhum)Filtra pelo tipo de jornada: kyc, kyb ou kyc_minor. Qualquer outro valor, inclusive kyc_representative, retorna 400 INVALID_REQUEST.
status_detaillista(nenhum)Um ou mais entre in_analysis, awaiting_correction, correction_submitted e correction_expired, separados por vírgula ou com o parâmetro repetido. Mantém apenas as execuções em pending_review cujo status_detail é um deles, e combina com status: com um status diferente de pending_review, a lista volta vazia. awaiting_beneficial_owners não é aceito aqui. Qualquer outro valor, inclusive um vazio entre vírgulas, retorna 400 INVALID_REQUEST.
correction_statelista(nenhum)O estado da rodada de correção mais recente: um ou mais entre open, in_progress, submitted e expired, com as mesmas regras de status_detail. Uma execução sem rodada nunca corresponde. Combina com status e com status_detail. Qualquer outro valor retorna 400 INVALID_REQUEST.
namestring (≤ 255)(nenhum)Correspondência por trecho do nome do cliente, sem diferenciar maiúsculas de minúsculas. Casa tanto com customers_person.name (kyc, kyc_minor, kyc_representative) quanto com customers_company.company_name (kyb).
tax_idstring(nenhum)Correspondência exata com o documento do cliente: o CPF de uma pessoa ou o CNPJ de uma empresa. O valor é normalizado no servidor para apenas caracteres alfanuméricos, em maiúsculas, portanto 123.456.789-09 e 12345678909 são a mesma busca. Depois da normalização, ele precisa ser um CPF (11 dígitos) ou um CNPJ (14 caracteres, numérico ou alfanumérico, com os dois últimos numéricos); qualquer outro formato retorna 400 INVALID_REQUEST antes de a consulta ser executada.

Os filtros são combináveis: ?reference_id=SOL-2026-00042&status=approved retorna as execuções aprovadas cujo reference_id corresponde exatamente; ?name=silva&status=in_progress retorna as execuções em andamento cujo nome do cliente contém silva.

O período e a lista de status restringem as linhas e o pagination.total da mesma forma. Um período sem status mantém a exclusão padrão: ?created_from=2026-09-01&created_to=2026-09-30 retorna as execuções criadas em setembro, menos as dos seis status omitidos.

Os cinco cadastros mais recentes de um conjunto de status, como os de um painel de últimos cadastros, saem de uma única chamada:

GET /api/v1/onboarding/executions?page=1&per_page=5&status=in_progress,processing,pending_review,pending_representative,approved,rejected,completed

Como a listagem é ordenada por created_at decrescente, a primeira página traz os cinco criados mais recentemente entre esses status. Como o status é explícito, approved e rejected entram, mesmo estando entre os omitidos por padrão.

{
"error": false,
"data": {
"items": [
{
"execution_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"flow_type": "kyc_minor",
"reference_id": "SOL-2026-00042",
"status": "in_progress",
"status_detail": "in_progress",
"steps_completed": 5,
"steps_total": 9,
"customer": {
"full_name": "Marcus Vinicius Sales",
"tax_id": "23117555854",
"tax_id_masked": "***175558**",
"email": "msales@exemplo.com.br",
"is_pep": false,
"has_foreign_tax_obligation": false
},
"data_divergences": [],
"correction": null,
"created_at": "2026-03-31T09:00:00Z",
"updated_at": "2026-03-31T09:41:00Z",
"started_at": "2026-03-31T09:03:00Z",
"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": "***456789**",
"email": "joao@exemplo.com"
}
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 1,
"total_pages": 1
}
}
}

Os metadados de paginação ficam aninhados em data.pagination (page, per_page, total, total_pages). O bloco decision existe apenas no detalhe: obtenha-o pelo endpoint de consulta individual.

O bloco customer tem o mesmo formato na listagem e no endpoint de detalhe por id. Na listagem, só os campos de identificação de resumo vêm preenchidos (full_name, tax_id, tax_id_masked, email, is_pep, has_foreign_tax_obligation). Os sub-blocos de endereço, documento e biometria, mais o envelope decision, só são retornados por GET /api/v1/onboarding/executions/{execution_id}. O campo customer é omitido (em vez de vir como null) nas linhas em que ainda não há registro de cliente vinculado (por exemplo, sondagens ou sessões abandonadas antes do primeiro passo). Nas execuções kyb, tax_id é o documento da empresa (o CNPJ) e email é omitido (empresas não carregam e-mail no registro de cliente).

O campo tax_id é o documento bruto, sem máscara: em uma linha de pessoa, o documento de identidade (o CPF), em uma linha de empresa, o registro da empresa (o CNPJ). O tax_id_masked legado continua sendo emitido por compatibilidade retroativa e será removido em uma versão futura; integrações novas devem consumir tax_id.

Mais cinco campos viajam em todos os itens e passam facilmente despercebidos:

CampoDescrição
status_detailO mesmo detalhe do status da leitura de detalhe. Consulte Detalhe do status.
correctionO resumo da rodada de correção mais recente, ou null. Consulte Correção cadastral.
updated_atA última alteração da execução ou da sua rodada de correção mais recente, a que for posterior.
linksO mesmo bloco { url, embed_url } da leitura de detalhe, montado a partir do link da própria linha sem consulta extra, e omitido da mesma forma quando a linha nunca recebeu um link. links.url é a forma de recuperar o link da jornada de uma linha.
data_divergencesSempre emitido e, na listagem, é sempre o array vazio, seja qual for o que a execução registrou. As linhas de divergência por item seriam uma consulta por item, portanto a listagem não as busca. Leia as divergências reais no endpoint de detalhe; não interprete um [] aqui como “sem divergências”.

O tax_id_masked de um item da listagem não é a máscara que o endpoint de detalhe produz. Aqui o valor mantém o meio do documento e esconde as pontas, portanto 12345678909 é publicado como ***456789**, contra ***7735 para o 11144477735 da leitura de detalhe. A máscara da listagem expõe mais dígitos do que a do detalhe. Trate as duas como strings de exibição do respectivo endpoint e não compare nenhuma delas.

As linhas de uma empresa kyb com a cadeia de beneficiários habilitada trazem também beneficial_owners, no mesmo formato da consulta (consulte Beneficiários finais).

Erros. Todos os filtros, exceto page e per_page, são validados antes de a consulta ser executada, e um valor recusado responde 400 INVALID_REQUEST. page e per_page são ajustados para valores seguros, em vez de rejeitados. Para status, created_from e created_to, as mensagens são as mesmas de Estatísticas de execuções.

HTTPCódigoCausa
400INVALID_REQUESTstatus fora dos 11 códigos, created_from ou created_to fora do formato AAAA-MM-DD ou com uma data que não existe, created_from posterior a created_to, reference_id com mais de 255 caracteres, flow_id que não é um UUID, name com mais de 255 caracteres, tax_id inválido, flow_type fora de kyc, kyb e kyc_minor, ou status_detail ou correction_state fora dos valores aceitos. O code não indica qual deles ocorreu; o message nomeia o filtro, salvo com Accept-Language em espanhol, que recebe o texto genérico de INVALID_REQUEST.
401MISSING_TOKENNem X-API-Key nem Authorization foi enviado, ou a credencial estava vazia.
401INVALID_API_KEYAPI Key inválida, revogada ou expirada.
502INTERNAL_ERRORA própria listagem falhou.
GET /api/v1/executions/stats

Retorna contagens agregadas sobre todo o conjunto filtrado, sem o teto da paginação. Use-o para montar os indicadores de um painel sem paginar a listagem inteira e agregar do lado do integrador. Aceita a API key do integrador ou um JWT de backoffice.

Este endpoint conta todas as execuções, inclusive as terminais. Diferente de Listagem de execuções, ele não aplica nenhuma exclusão padrão: approved, rejected, blocked, expired, cancelled e created aparecem todos em by_status. O total conta as execuções em todos os status, enquanto a listagem pública GET /api/v1/onboarding/executions omite esses seis status quando status não é enviado, então o total dela pode ser menor. As execuções de correção cadastral ficam fora de todas as contagens, como ficam fora das listagens.

ParâmetroTipoPadrãoDescrição
flow_idUUID(nenhum)Filtra pelo fluxo que originou as execuções.
flow_typestring(nenhum)Filtra pelo tipo de jornada: kyc, kyb, kyc_minor, kyc_representative.
statuslista(nenhum)Um ou mais códigos de status, separados por vírgula (status=approved,rejected) ou com o parâmetro repetido (status=approved&status=rejected); um único código continua aceito. Os códigos aceitos são created, in_progress, processing, pending_review, pending_representative, approved, rejected, blocked, completed, cancelled, expired. Qualquer outro valor retorna 400 INVALID_REQUEST.
created_fromdata (AAAA-MM-DD)(nenhum)Início do período de criação (created_at), inclusivo, no horário de Brasília (UTC-3): conta a partir das 00:00 desse dia. Pode vir sem created_to.
created_todata (AAAA-MM-DD)(nenhum)Fim do período de criação, inclusivo, no horário de Brasília: conta até o fim desse dia. Pode vir sem created_from.
status_idUUID(nenhum)Filtra pelo identificador do status. Prefira status.
queue_idUUID(nenhum)Filtra pela fila de análise. Tem precedência sobre queue.
queuestring(nenhum)Filtra pelo nome da fila de análise, resolvido para queue_id no servidor.

Os filtros são combináveis. status, created_from e created_to são validados: uma data fora do formato AAAA-MM-DD ou que não existe (como 2026-02-30 ou o ano 0000), um created_from posterior a created_to ou um status fora dos 11 códigos retornam 400 INVALID_REQUEST, com as mensagens listadas em Erros, abaixo. Os demais não são recusados: um UUID malformado em flow_id, status_id ou queue_id, e um nome de fila que não existe, são ignorados, e a resposta é 200 com as contagens sem aquele filtro; um flow_type desconhecido zera as contagens. A resposta sozinha não distingue um filtro aplicado de um ignorado, portanto confira os valores antes de enviar.

Cada mês de um gráfico mensal é uma chamada com o período daquele mês. Para setembro:

GET /api/v1/executions/stats?created_from=2026-09-01&created_to=2026-09-30

O total e as contagens dessa resposta cobrem as execuções criadas de 1º a 30 de setembro, no horário de Brasília, com os dois dias incluídos.

{
"error": false,
"data": {
"total": 1228,
"by_status": {
"created": 9,
"in_progress": 5,
"processing": 1,
"pending_review": 10,
"pending_representative": 8,
"approved": 204,
"rejected": 24,
"blocked": 0,
"completed": 1,
"cancelled": 250,
"expired": 716
},
"by_risk": {
"approve": 214,
"review": 6,
"block": 0
},
"by_type": {
"kyc": 682,
"kyb": 400,
"kyc_minor": 74,
"kyc_representative": 72
}
}
}

total é a contagem sobre o conjunto filtrado e é igual à soma de by_status. by_type só tem chaves para os quatro tipos acima, portanto ele soma total apenas quando não há execuções de outro tipo: uma execução kyb_representative, por exemplo, entra em total e em by_status, mas em nenhuma chave de by_type.

by_risk não soma total, de propósito. Ele classifica apenas as execuções com decisão final de risco (approved, rejected, completed ou blocked) e com score de risco não nulo; as execuções em andamento, e as decididas sem score, ficam fora das três faixas. Os cortes vêm dos limites review_threshold e block_threshold da matriz de risco da instância, no domínio default:

FaixaCondição
approvescore abaixo de review_threshold
reviewscore maior ou igual a review_threshold e abaixo de block_threshold
blockscore maior ou igual a block_threshold

Os cortes são lidos da matriz no momento da consulta, portanto uma contagem por faixa só é comparável ao longo do tempo se a matriz não foi recalibrada nesse intervalo: a recalibração não reescreve scores passados, apenas muda onde os cortes caem. Essas faixas também não são o risk_level da consulta de uma execução, que usa a régua fixa de 150, 350 e 700, então as duas classificações podem divergir para a mesma execução.

HTTPCódigoCausa
400INVALID_REQUESTstatus com um código fora dos 11 aceitos, created_from ou created_to fora do formato AAAA-MM-DD ou com uma data que não existe, ou created_from posterior a created_to.
401MISSING_TOKEN / INVALID_API_KEYNem X-API-Key nem Authorization foi enviado, ou a credencial é inválida.
500INTERNAL_ERRORA instância não tem matriz de risco configurada no domínio default, portanto as faixas não podem ser calculadas. O endpoint falha de forma explícita em vez de responder faixas zeradas, que se leriam como “nenhum risco em lugar nenhum”. Contate o suporte se isso acontecer.

O message do 400 é um destes textos, salvo com Accept-Language em espanhol, que recebe o texto genérico de INVALID_REQUEST:

Causamessage
status fora dos 11 códigosstatus inválido (valores aceitos: created, in_progress, pending_review, approved, rejected, cancelled, expired, processing, pending_representative, blocked, completed)
created_from inválidocreated_from inválido: use o formato AAAA-MM-DD com uma data existente
created_to inválidocreated_to inválido: use o formato AAAA-MM-DD com uma data existente
created_from posterior a created_toperíodo inválido: created_from é posterior a created_to
POST /api/v1/executions/{execution_id}/cancel

Transiciona a execução para o estado cancelled. Quando a execução tem execuções filhas (o representante legal sob uma execução pai kyc_minor), a chamada é aplicada em cascata e as cancela na mesma operação.

Como o índice único parcial idx_executions_active_person_flow exclui os status terminais, o cancelamento libera o slot (customer_person_id, flow_id). Após um cancelamento bem-sucedido, um novo POST /onboarding/sessions para o mesmo CPF e fluxo deixa de retornar 409 ACTIVE_SESSION_EXISTS e uma nova sessão pode ser criada. Casos de uso típicos: reabrir uma jornada kyc_minor para novos testes, recuperar-se de um envio incorreto ou rotacionar uma sessão que o cliente final não conseguiu concluir a tempo.

Um evento de webhook onboarding.cancelled é emitido em caso de sucesso; consulte Famílias de eventos.

Aceita uma das duas opções:

  • X-API-Key (gl_live_* ou gl_test_*); ou
  • Authorization: Bearer <JWT> (JWT do backoffice).

Respeita o header Idempotency-Key; consulte Idempotência.

O corpo é opcional. O único campo aceito é reason, registrado na trilha de auditoria e no payload do evento onboarding.cancelled:

{
"reason": "Duplicate registration, recreating with correct data"
}
CampoTipoObrigatórioDescrição
reasonstring (máx. 500)NãoMotivo em texto livre registrado na auditoria e no payload do evento.
{
"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"
}
}
CampoTipoDescrição
execution_idUUIDExecução que foi cancelada.
cancelled_childrenarray de UUIDUUIDs das execuções filhas canceladas em cascata (o representante legal sob um kyc_minor). A chave é omitida por completo, e não enviada como [], quando nada foi cancelado em cascata: nenhum filho, ou nenhum que ainda pudesse ser cancelado. Teste a presença da chave antes de ler o tamanho dela.
status_codestringSempre cancelled em caso de sucesso.
cancelled_atISO 8601Timestamp do cancelamento.
HTTPCódigoCausa
400INVALID_IDO UUID do path está malformado.
400INVALID_REQUESTO corpo da requisição está presente, mas malformado.
400VALIDATION_FAILEDO reason tem mais de 500 caracteres.
401MISSING_TOKEN / INVALID_API_KEYAutenticação ausente, inválida ou expirada.
404NOT_FOUNDNenhuma execução com o UUID informado.
409EXECUTION_TERMINALExecução já está em estado terminal. O conjunto de recusa é exatamente quatro códigos: approved, rejected, expired e cancelled.
429RATE_LIMITEDLimite de requisições excedido.

O cancelamento aceita um conjunto de estados mais amplo do que a coluna de terminais da tabela do ciclo de vida sugere. blocked é um deles: cancelar uma execução blocked funciona e responde 200, e não 409. O mesmo vale para cancelar uma execução em completed, e em processing, pending_review ou pending_representative. O conjunto aceito completo é created, in_progress, pending_review, pending_representative, processing, completed e blocked.

Webhooks são notificações HTTP enviadas pela Guardline ao backend do integrador quando uma execução muda de estado. Os payloads são intencionalmente enxutos: contêm os identificadores do evento e um pequeno bloco de dados. Os dados completos devem ser obtidos pelo endpoint de consulta.

Duas famílias coexistem após a reescrita:

  • onboarding.*: estado do onboarding como um todo. Emitidos em toda execução.
  • linkage.*: estado de um sujeito vinculado dentro de um fluxo pareado. Abrange o representante legal do kyc_minor. Emitidos na execução pai, com o resultado do sujeito vinculado refletido no payload.

A mesa de conformidade emite uma terceira família, cmp.case.*, que é interna e não chega ao integrador. Ela está nomeada aqui apenas para que a ausência dela no seu endpoint não seja lida como entrega perdida.

EventoDescrição
onboarding.startedSessão criada via POST /sessions.
onboarding.completedTodas as etapas enviadas; motor de decisão em processamento.
onboarding.approvedAprovado pelo motor ou por um analista.
onboarding.rejectedReprovado pelo motor ou por um analista.
onboarding.reviewEncaminhado para revisão manual.
onboarding.blockedJornada interrompida por uma regra de segurança.
onboarding.expiredSessão expirada antes da conclusão.
onboarding.cancelledCancelado pelo integrador ou por um administrador.
EventoDescrição
linkage.pendingO menor de idade concluiu sua jornada e o pai passou para pending_representative. A URL do representante foi criada em POST /sessions; este evento marca a transição do pai, não a criação do filho.
linkage.startedO representante legal acessou seu link e enviou a primeira etapa.
linkage.completedO representante legal concluiu sua jornada, o que dispara a consolidação do pai. O evento é emitido antes de as condições de consolidação serem avaliadas, portanto ele não garante que a consolidação foi executada: quando uma condição falha, não há consolidação.
linkage.rejectedUm analista reprovou a execução do representante na revisão manual.
linkage.expiredA execução do representante expirou antes da conclusão.

Em uma empresa kyb que aguarda os beneficiários finais, os mesmos eventos saem na empresa. linkage.pending marca o estacionamento e traz data.beneficial_owners (total e pending). linkage.started, linkage.completed, linkage.rejected e linkage.expired acompanham cada beneficiário e trazem data.linked: execution_id da jornada do beneficiário, role (beneficial_owner), round e state. O onboarding.completed da empresa só sai quando ela é liberada para a decisão, e uma empresa cujas rodadas se esgotam recebe onboarding.expired.

A reescrita da API do integrador substituiu representative.{pending, started, completed} pela família linkage.* acima. Os tipos de evento legados não são emitidos nos novos caminhos de código, mas entregas históricas de webhook nos logs de auditoria do integrador podem referenciá-los. Trate as duas taxonomias como equivalentes em termos de informação para dados históricos; apenas linkage.* é emitido hoje.

Os eventos seguem uma ordem de emissão estrita dentro de cada execução. Por causa das retentativas, os eventos podem chegar fora de ordem ao endpoint do integrador. O campo event_sequence (crescente de forma monotônica por execução) deve ser usado como critério canônico de ordenação, e não o timestamp ou a ordem de entrega. Para reconhecer um evento repetido, por outro lado, use o event_id do corpo; consulte Deduplicação.

Aprovação automática:

onboarding.started (1) session created
onboarding.completed (2) all steps submitted
onboarding.approved (3) approved by engine

Reprovação:

onboarding.started (1)
onboarding.completed (2)
onboarding.rejected (3)

Revisão manual e, depois, resolução:

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)

Os eventos linkage.* referenciam o execution_id pai (do menor de idade), portanto o progresso do representante aparece na cadeia do pai. Isso não vale para todos os eventos que o pareamento produz: um filho representante emite os seus próprios onboarding.expired, onboarding.approved e onboarding.rejected no execution_id do filho.

Como o event_sequence é calculado por id de execução, esses eventos do filho caem em uma cadeia de sequência separada, que recomeça em 1. Um integrador que ordena por event_sequence e indexa pelo pai verá um evento avulso carregando um execution_id desconhecido e sequência 1. Esses payloads carregam parent_execution_id, que é o campo pelo qual correlacionar.

Aprovação automática:

onboarding.started (1) session created
linkage.pending (2) minor completed; parent in pending_representative
linkage.started (3) representative accessed their link
linkage.completed (4) representative finished
onboarding.completed (5) consolidation; decision engine processing
onboarding.approved (6) approved by engine

Reprovação:

onboarding.started (1)
linkage.pending (2)
linkage.started (3)
linkage.completed (4)
onboarding.completed (5)
onboarding.rejected (6)

Revisão manual e, depois, resolução:

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 o módulo do menor de idade:

onboarding.started (1)
onboarding.blocked (2)

Expirado aguardando o representante:

onboarding.started (1)
linkage.pending (2)
onboarding.expired (3) parent expired
linkage.expired (4) representative's link expired

O onboarding.expired é despachado antes do linkage.expired, e não depois dele.

Representante reprovado pelo analista:

onboarding.started (1)
linkage.pending (2)
linkage.started (3)
linkage.completed (4)
onboarding.completed (5)
onboarding.review (6)
onboarding.rejected (7) parent decision
linkage.rejected (8) analyst rejected the representative

Como na expiração, o onboarding.rejected é despachado antes do linkage.rejected.

{
"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"
},
"liveness": {
"status": "executed",
"result": "passed"
}
}
}
CampoDescrição
eventTipo do evento.
execution_idIdentificador da execução pai. Para eventos linkage.* em kyc_minor, é o ID da execução do menor de idade. O identificador do filho vinculado não vem no bloco data dos eventos de vínculo do kyc_minor; só os eventos dos beneficiários finais de uma empresa kyb o trazem, em data.linked.execution_id. Um consumidor que precise do id do filho tem que buscá-lo.
flow_typeTipo de jornada.
reference_idIdentificador do integrador, conforme enviado na criação da sessão.
event_idUUID do evento, o mesmo valor do cabeçalho X-Guardline-Event-ID. Trafega dentro do corpo assinado, portanto a assinatura HMAC o cobre, e é idêntico em todas as retentativas da mesma entrega. É a chave de deduplicação preferida. As entregas anteriores à introdução do campo não trazem a chave; consulte Deduplicação.
event_sequenceNúmero sequencial do evento dentro da execução, para ordenação canônica. Junto com execution_id, identifica o evento de forma única e é a chave de deduplicação alternativa quando falta event_id.
timestampTimestamp de emissão do evento em ISO 8601.
dataBloco aditivo, ausente quando estaria vazio. Conteúdo abaixo.

Tudo aqui é aditivo: uma chave simplesmente não aparece quando não há nada a reportar, então um receptor escrito para um envelope mais antigo continua funcionando.

ChaveQuando está presenteConteúdo
data.decisionAssim que a execução tem uma decisão, e em vários eventos que não têm nenhumaresult, risk_level, risk_score, decided_by, decided_at. O result nem sempre contém uma decisão; veja abaixo.
data.livenessNos eventos terminais, quando o fluxo tem etapa de prova de vida ou nãoResultado da prova de vida. Detalhado abaixo.

Apesar do nome, data.decision nem sempre carrega uma decisão. Em vários eventos, o result carrega um status de execução: pending_representative, in_progress, completed, blocked, cancelled e expired aparecem ali. Esses mesmos eventos enviam risk_level: "low", que é um valor padrão, e não uma avaliação de risco real. Não ramifique o result apenas contra o vocabulário de decisões, e não leia risk_level como uma avaliação a menos que o result realmente contenha uma decisão.

Dois eventos são exceção ao formato acima: linkage.rejected e linkage.expired carregam apenas decision.

Presente nos eventos que carregam uma decisão ou uma transição de estado de vínculo (onboarding.approved/rejected/review/blocked e linkage.*).

CampoDescrição
resultapproved, rejected, pending_review, blocked, ou o estado do vínculo (pending_representative, in_progress, completed, expired).
risk_levelDerivado do score da decisão por uma régua fixa: low (score até 150), medium (até 350), high (até 700), critical (acima de 700). Uma execução sem score publica low.
risk_scoreO score inteiro do qual risk_level deriva, nas decisões do motor e nas de analista. Omitido quando a execução nunca foi pontuada.
decided_byengine nas decisões do motor e analyst nas decisões de analista, no webhook e no endpoint de consulta.
decided_atMomento da decisão, em ISO 8601.

Presente nos eventos terminais: onboarding.completed, onboarding.approved, onboarding.rejected, onboarding.review e onboarding.blocked emitidos pelo motor, os mesmos eventos quando a decisão é de um analista, e onboarding.expired. A etapa de prova de vida (iProov) é modular por fluxo: uma jornada pode executá-la, pulá-la por configuração ou pertencer a uma variante de fluxo sem ela, e o integrador pode ligá-la ou desligá-la por sessão com o campo biometric de POST /api/v1/onboarding/sessions. Este bloco permite distinguir “não executada por configuração” de “falhou”.

statusSignificado
executedA prova de vida rodou e passou. A conclusão da etapa exige evidência aprovada, portanto executed implica aprovação; result: "passed" o acompanha.
not_executedO fluxo tem a etapa habilitada, mas a jornada terminou sem concluí-la (falha, limite de tentativas, abandono).
skipped_by_configA etapa existe no fluxo, mas foi desligada por configuração: desabilitada no fluxo, desabilitada nesta sessão ("biometric": false) ou uma condição do fluxo não atendida.
not_configuredA variante do fluxo não tem etapa de prova de vida.

O campo result só está presente quando status é executed, e hoje só assume passed. A semântica é de melhor esforço: se o bloco não puder ser resolvido no momento da emissão, ele é omitido, e a ausência não deve quebrar o parser do integrador.

Notas:

  • A ausência do bloco significa “desconhecido”, nunca not_configured. Envelopes emitidos antes deste contrato também não o trazem. Não deduza a configuração pela ausência do objeto liveness; “a jornada não tem prova de vida” é sinalizado exclusivamente pelo valor explícito not_configured.
  • onboarding.cancelled deliberadamente não carrega o bloco: ele vale para os eventos terminais que carregam um desfecho, e um cancelamento não é um desfecho.
CabeçalhoDescrição
Content-TypeSempre application/json.
X-Guardline-Event-IDUUID estável do evento, idêntico em todas as tentativas do mesmo evento e sempre igual ao event_id do corpo. O cabeçalho continua sendo enviado, mas a assinatura não o cobre: faça a deduplicação pelo event_id do corpo assinado e use o cabeçalho para correlacionar logs. Consulte Deduplicação.
X-Guardline-Delivery-IDO identificador da linha de entrega. Ele é estável em todas as tentativas daquela entrega, e não único por tentativa: uma retentativa carrega o mesmo valor que a primeira tentativa carregou. Não faça a deduplicação por ele, ou toda retentativa será descartada em silêncio.
X-Guardline-Attempt-NumberNúmero sequencial da tentativa (1 para a primeira entrega, 2 para a primeira retentativa e assim por diante). É o único discriminador por tentativa.
X-Guardline-TimestampTimestamp Unix do momento da assinatura (apenas no modo hmac).
X-Guardline-SignatureAssinatura HMAC-SHA256 em hexadecimal, feita com o segredo vigente (apenas no modo hmac).
X-Guardline-SignaturesLista separada por vírgula com uma assinatura por segredo válido, a do segredo vigente primeiro (apenas no modo hmac). Fora de uma rotação, tem um único item, igual a X-Guardline-Signature. Durante a janela de uma rotação, traz também a assinatura feita com o segredo anterior. Aceite a entrega se qualquer item conferir. Consulte Rotação de credenciais.
Ocp-Apim-Subscription-KeyChave de subscrição do API gateway. Enviada em todas as entregas no modo mtls_apim, em que é obrigatória.
X-API-KeyChave estática que o endpoint do integrador espera, quando há uma configurada no endpoint de webhook. Opcional e independente da assinatura HMAC.

Cada evento carrega um UUID próprio, event_id, dentro do corpo da entrega, ao lado de event_sequence. É o mesmo valor que trafega no cabeçalho X-Guardline-Event-ID, permanece idêntico em todas as retentativas da mesma entrega e nunca se repete entre dois eventos. Como o que se assina é {timestamp}.{body}, a assinatura cobre o event_id; o cabeçalho fica fora do material assinado, então uma entrega reproduzida com o cabeçalho adulterado seria verificada como a original se o receptor tirasse a identidade do evento apenas dele.

Chaves de deduplicação, em ordem de preferência:

  1. event_id do corpo, autenticado pela assinatura. É a chave recomendada nos dois modos de autenticação: o campo é injetado no corpo de toda entrega, qualquer que seja o modo.
  2. execution_id + event_sequence, quando o corpo não traz event_id. O par identifica um evento de forma única: a sequência é alocada por execução sob um bloqueio, é estável entre as retentativas da mesma entrega e nunca é reutilizada para dois eventos da mesma execução.

O cabeçalho X-Guardline-Event-ID continua sendo enviado e é sempre igual ao event_id do corpo. Ele serve para correlacionar logs e depurar; não o use como única chave de deduplicação.

As entregas anteriores à introdução do campo não trazem a chave event_id no corpo. O receptor deve tolerar a ausência e recorrer à segunda chave, em vez de rejeitar a entrega.

Não faça a deduplicação por X-Guardline-Delivery-ID: ele identifica a linha de entrega, não o evento, e descartaria toda retentativa em silêncio.

A Guardline suporta dois modos de autenticação para a entrega de webhooks, hmac e mtls_apim. O modo é configurado por endpoint de webhook, e uma restrição do banco de dados recusa qualquer outro valor. Não existe modo de entrega com token bearer ou OAuth: a Guardline nunca define um header Authorization em uma requisição de webhook.

Os dois modos são exclusivos, e não cumulativos. O hmac assina o corpo; o mtls_apim autentica na camada de transporte e não envia nenhum header de assinatura.

ModoAssinatura HMACCertificado de clienteChave de subscrição
hmac (padrão)SimNãoNão
mtls_apimNãoSimSim

A autenticação HMAC usa um segredo compartilhado provisionado durante a configuração do integrador no painel administrativo.

Processo de verificação no receptor:

  1. Extraia o cabeçalho X-Guardline-Timestamp.
  2. Rejeite se o timestamp for mais antigo que 5 minutos em relação ao relógio do servidor.
  3. Concatene o timestamp e o corpo da requisição: {timestamp}.{body}.
  4. Calcule o HMAC-SHA256 da concatenação usando o segredo compartilhado.
  5. Codifique o resultado em hexadecimal.
  6. Compare com o cabeçalho X-Guardline-Signature usando comparação em tempo constante.
  7. Rejeite se houver divergência.

O corpo assinado inclui o event_id do evento, portanto a mesma verificação autentica a identidade usada depois na deduplicação. O cabeçalho X-Guardline-Event-ID não faz parte do material assinado.

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)

O TLS mútuo autentica a entrega na camada de transporte, em vez de assinar o corpo. O modo é voltado a um API gateway, e não a um listener HTTPS simples, e exige o cabeçalho Ocp-Apim-Subscription-Key descrito acima.

A configuração é do lado do operador. A Guardline não emite um certificado para o integrador confiar; ela apresenta o que lhe foi entregue:

  1. O operador faz o upload, pela API administrativa, do certificado de cliente e da chave privada que a Guardline deve apresentar.
  2. O operador faz o upload de um bundle de CA. Isso é opcional e, quando presente, é usado pela Guardline para verificar o certificado do servidor de destino. Ele não é distribuído ao integrador.
  3. O operador configura a chave de subscrição do gateway, que é obrigatória nesse modo.
  4. Em cada entrega, a Guardline apresenta o certificado de cliente enviado e manda a chave de subscrição em Ocp-Apim-Subscription-Key.
  5. O gateway valida o certificado contra o trust store com o qual foi configurado.

Nada na Guardline define ou verifica um CN, portanto não condicione a aceitação a esse campo: o certificado carrega o subject que o operador enviou. A identidade do par, do lado da Guardline, é a verificação comum de hostname contra a URL configurada.

Quando o mtls_apim está habilitado, os cabeçalhos X-Guardline-Signature e X-Guardline-Timestamp não são enviados.

O endpoint de webhook é configurado no painel administrativo da Guardline. Requisitos:

RequisitoDetalhe
ProtocoloHTTPS obrigatório.
Versão do TLS1.2 ou superior.
Resposta de sucessoQualquer código 2xx (corpo ignorado).
RedirecionamentosNão são seguidos. Um 301 ou 302 é recusado.
Cabeçalhos da respostaEnviar a linha de status em até 5 segundos.
TimeoutConcluir a resposta em até 10 segundos.
IdempotênciaDeduplicar as entregas usando o event_id do corpo assinado; se a entrega não o trouxer, usar execution_id + event_sequence. Consulte Deduplicação.

Recomendação de implementação: receba o webhook, enfileire para processamento assíncrono e responda imediatamente com 200. Isso evita timeouts e garante entrega confiável.

Duas dessas linhas custam tempo às integrações, então vale enunciá-las com clareza.

O orçamento de resposta não é uma única janela de 10 segundos. O transporte também aplica um timeout de 5 segundos para os cabeçalhos da resposta, portanto um receptor que leva 7 segundos para enviar a linha de status falha, mesmo estando dentro dos 10 segundos documentados. Envie os cabeçalhos cedo e conclua o corpo depois.

Os redirecionamentos são recusados de imediato, e não seguidos. Um endpoint atrás de um 301 ou 302 nunca recebe a entrega. Pior: a recusa aparece como erro de transporte, e não como status HTTP, portanto conta como passível de retentativa e queima todo o orçamento de retentativas antes de a entrega ser esgotada. Configure a URL final.

Quando certificados mTLS ou o segredo HMAC são rotacionados, as retentativas em andamento de entregas já despachadas continuam usando as credenciais que estavam ativas no momento do despacho. Novas entregas passam a usar as credenciais atualizadas imediatamente.

A rotação do segredo HMAC não derruba o receptor que ainda não trocou o valor. O segredo anterior continua válido por uma janela de sobreposição, de 24 horas por padrão, e durante ela cada entrega leva em X-Guardline-Signatures uma assinatura com o segredo novo e outra com o anterior. X-Guardline-Signature continua sendo só a do segredo novo. Para trocar o segredo sem perder entregas, verifique contra todos os itens de X-Guardline-Signatures, publique o valor novo no receptor dentro da janela e só então peça à Guardline o encerramento do segredo anterior. Em caso de vazamento, a Guardline pode revogar o anterior na hora, o que interrompe qualquer receptor que ainda o use.

Um operador tem dois caminhos de reenvio, e eles não se comportam da mesma forma:

CaminhoCredenciais usadasLinha de entrega
Reenvio remontado a partir da configuração vigenteA configuração como ela está agora, portanto uma rotação é considerada.Cria uma nova entrega. A original é mantida intacta para fins de auditoria.
Reenfileiramento da entrega existenteO snapshot de credenciais capturado quando a entrega foi enfileirada.Reutiliza a mesma linha.

A distinção importa depois de uma rotação: um operador que rotaciona um certificado e então reenfileira a entrega existente a reenvia com as credenciais revogadas, e ela falha de novo pelo mesmo motivo.

O caminho de remontagem tem uma consequência para o receptor. Como ele cria uma nova entrega, o reenvio carrega um novo id de evento e um novo número de sequência. Isso quebra as duas chaves nas quais este documento diz aos integradores para se apoiarem: o event_id (no corpo e no cabeçalho X-Guardline-Event-ID) deixa de corresponder ao original, portanto a deduplicação não vai reconhecer o reenvio como a repetição de um evento já processado, e o event_sequence novo não posiciona o evento onde ele pertence na cadeia da execução.

Quando uma entrega falha, a Guardline a repete com backoff exponencial. O intervalo abaixo é a espera depois que a tentativa numerada falha:

Tentativa que falhouEspera antes da próxima tentativa
130 segundos
22 minutos
310 minutos
430 minutos
51 hora
62 horas
74 horas
88 horas
912 horas
10Nenhuma. A entrega é marcada como exhausted.

Todos os intervalos acima são nominais, por dois motivos.

Cada atraso agendado é multiplicado por um fator de jitter aleatório entre 0.8 e 1.2, portanto um intervalo nominal de 1 hora cai em algum ponto entre 48 e 72 minutos.

O despachante drena a fila em um tick de 30 segundos, e não de forma contínua. Uma entrega cujo atraso já se esgotou ainda espera pelo próximo tick, e o mesmo tick atrasa a primeira entrega de qualquer evento em até 30 segundos depois do próprio evento. Não trate um webhook como um sinal de tempo real.

Nem toda falha rende a escada acima. Ela se aplica a timeouts, erros de transporte, respostas 5xx e a exatamente dois códigos 4xx. Qualquer outro 4xx é tratado como não passível de retentativa e esgota a entrega na primeira tentativa, sem nenhuma retentativa. Isso cobre a falha real mais provável: um receptor que rejeita o envelope, uma chave de entrada rotacionada do lado do integrador ou uma rota que ainda não está no ar. Todos eles consomem a entrega imediatamente, em vez de deixar uma janela para corrigir o problema. As duas exceções 4xx que são repetidas são 408 Request Timeout e 429 Too Many Requests.

Após 10 tentativas sem sucesso, a entrega é marcada como exhausted e um alerta interno é gerado. As notificações continuam acessíveis pelo endpoint de polling (veja Polling de notificações). O reenvio manual também está disponível no painel administrativo.

O endpoint de polling é o mecanismo de fallback para garantir que nenhuma notificação se perca, independentemente de falhas de webhook. Ele também pode ser usado como mecanismo principal de integração por integradores que preferem um modelo pull.

O polling lê as mesmas entregas que o webhook envia, portanto herda a assinatura de eventos da configuração. Uma configuração de webhook pode assinar só alguns eventos; um evento fora da lista é descartado antes de virar entrega, e então não é enviado por webhook nem aparece aqui. Uma configuração sem lista assina todos os eventos, que é o comportamento de toda configuração anterior a esse recurso. Se você usa o polling como rede de segurança, confirme com a Guardline que a configuração assina todos os eventos que você trata.

GET /api/v1/onboarding/notifications

Esse endpoint usa paginação baseada em cursor.

ParâmetroTipoPadrãoDescrição
statusstringpendingFiltra por estado: pending, retrying, exhausted. Separados por vírgula para múltiplos valores.
limitinteger50Itens por página (1 a 200; valores fora da faixa voltam para 50).
after_cursorstring(nenhum)Cursor para a próxima página: o delivery_id codificado em base64 retornado em next_cursor pela chamada anterior. Leia o alerta sobre paginação abaixo antes de usá-lo.

O failed é aceito pelo filtro, mas o despachante nunca atribui esse estado, portanto pedi-lo não retorna nada. O estado terminal de que um integrador realmente precisa é exhausted, que funciona.

A paginação por cursor só é confiável para a primeira página. A consulta ordena as linhas por data de criação decrescente, mas compara o cursor com a chave primária UUID aleatória. As duas ordenações não têm nada a ver uma com a outra, portanto toda página depois da primeira é um subconjunto arbitrário que ao mesmo tempo pula linhas e as repete. Não pagine em profundidade. Busque a primeira página, confirme o processamento do que você tratou e busque a primeira página de novo: as linhas confirmadas saem do resultado, portanto a fila se esvazia sem cursor.

Resposta (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 é uma string base64 quando podem existir mais páginas (retornada quando a página está cheia), ou null quando não há mais itens. Devolva-o como after_cursor para buscar a próxima página, sujeito ao alerta acima. Cada item carrega flow_type e reference_id, a contagem atual de attempts e o timestamp da entrega.

O attempts é incrementado antecipadamente na falha, portanto ele corre um à frente das tentativas efetivamente feitas: uma linha que mostra 3 foi tentada duas vezes.

O last_attempt_at não é retornado. O campo vem do timestamp de entrega, que só é gravado em caso de sucesso, e este endpoint retorna apenas linhas não entregues, portanto ele está estruturalmente sempre ausente. Não escreva um consumidor que dependa dele.

A listagem não está restrita à configuração de webhook do integrador. Em uma instância que também executa a saída de gestão de casos, as entregas endereçadas àquele receptor interno aparecem na mesma listagem, carregando nomes de evento fora do catálogo documentado aqui. Filtre pelos nomes de evento que você reconhece e ignore o restante.

POST /api/v1/onboarding/notifications/{delivery_id}/ack

Marca uma notificação como processada. Após a confirmação, a notificação deixa de aparecer nas consultas com status pending. A operação é idempotente: chamadas repetidas para o mesmo delivery_id retornam sucesso, sem efeitos colaterais.

Resposta (200):

{
"error": false,
"data": {
"delivery_id": "d4e5f6a7-b8c9-0123-def0-123456789abc",
"status": "acknowledged"
}
}

KYC menor de idade com notificação entregue pela Guardline

Seção intitulada “KYC menor de idade com notificação entregue pela Guardline”
CódigoSignificadoOrientação
200SucessoProcesse normalmente.
201Recurso criadoProcesse normalmente.
400Requisição inválidaCorrija o payload. Não faça retry.
401Falha na autenticaçãoVerifique a API Key (ou o JWT em resend-representative-link). Não faça retry.
403ProibidoA credencial foi verificada, mas não carrega nenhum papel autorizado (JWT) ou nenhum escopo exigido (API key). Não faça retry.
404Recurso não encontradoVerifique os identificadores. Não faça retry.
405Método não permitidoO path existe, mas não para este verbo. Corrija a chamada. Não faça retry.
409ConflitoTrate conforme o código de erro específico.
410Recurso não mais disponível (Gone)O recurso era válido, mas não está mais acessível (por exemplo, link expirado).
413Payload muito grandeO corpo excedeu o limite global de 13 MiB ou o limite da rota. Não faça retry.
422Não processávelO corpo foi interpretado, mas viola uma regra de domínio. Corrija a requisição. Não faça retry.
429Limite de requisições excedidoDois limitadores diferentes respondem 429; veja abaixo.
500Erro internoFaça retry com backoff exponencial (máximo de 3 tentativas).
502Erro interno durante o processamentoFaça retry com backoff exponencial.

O 429 precisa ser separado, porque apenas uma das duas origens dele pode ser resolvida com espera. O limitador por IP da resolução do link envia um header Retry-After e se libera sozinho: aguarde o intervalo indicado e tente de novo. O limite de reenvios em POST .../resend-representative-link é um contador vitalício na execução, e não uma janela. Ele não envia Retry-After, nunca é reiniciado, e repetir nunca pode dar certo. Leia esse 429 como “esta execução não tem mais reenvios” e pare; consulte Limite máximo.

Nota: os campos message dos erros são localizados por Accept-Language (veja Idioma das mensagens de erro). Faça a correspondência pelo code para ramificações programáticas.

CódigoHTTPDescrição
MISSING_TOKEN401Nenhuma credencial chegou ao handler: nem X-API-Key nem Authorization foi enviado, ou a credencial estava vazia. Este é o 401 comum em todo endpoint autenticado, inclusive nos que usam API key. Ele também cobre o JWT ausente em resend-representative-link.
INVALID_API_KEY401API Key inválida, revogada ou expirada.
INVALID_TOKEN401Um JWT Bearer foi enviado e falhou na verificação: assinatura inválida, emissor incorreto ou expirado.
FORBIDDEN403O JWT não possui o role necessário (admin ou supervisor).
FLOW_ID_REQUIRED400flow_type sem flow_id numa instância com mais de um fluxo ativo desse tipo.
INVALID_REQUEST400JSON malformado, campo desconhecido ou tipo de campo incorreto.
INVALID_ID400Um UUID do path está malformado.
VALIDATION_FAILED400Falha na validação de campos, incluindo uma requisição que não envia nem flow_id nem flow_type.
INVALID_TAX_ID400primary.tax_id não corresponde ao formato emitido pelo país do fluxo resolvido.
INVALID_IDEMPOTENCY_KEY400Header Idempotency-Key com mais de 255 caracteres.
LINKED_SUBJECTS_REQUIRED400Requisição kyc_minor com linked_subjects ausente, vazio ou acima do tamanho permitido, ou com role incorreto.
INVALID_FLOW_TYPE400O flow_id resolve para um tipo de fluxo de uso interno.
MISSING_REQUIRED_FIELDS400kyc_minor sem primary.tax_id ou primary.full_name.
PHONE_POLICY_VIOLATION422Um fluxo mobile-only recebeu um número de telefone fixo.
UNPROCESSABLE422Uma política de URL foi violada.
CPF_MINOR_REPRESENTATIVE_MATCH422O CPF do representante legal é igual ao CPF do menor de idade.
LINKED_SUBJECT_RELATIONSHIP_REQUIRED422Uma entrada kyc_minor omitiu linked_subjects[0].subject.relationship.
REPRESENTATIVE_AGE_BELOW_MINIMUM422O birth_date do representante legal indica idade inferior a 18 anos.
ACTIVE_SESSION_EXISTS409Já existe sessão ativa para o mesmo sujeito principal e fluxo.
CUSTOMER_NOT_FOUND404Nenhum cadastro ativo para o id informado, nas rotas de sessão por id do cliente. Distinto de FLOW_NOT_FOUND, porque os dois podem faltar na mesma requisição.
DUPLICATE409Uma restrição de unicidade recusou a escrita, normalmente duas chamadas concorrentes disputando a criação da mesma linha.
EXECUTION_NOT_FOUND404Execução não encontrada. A leitura não carrega nenhum predicado de tenant, portanto esta é uma verificação de existência, e não um teste de propriedade; o isolamento é o banco de dados por instância (consulte Ambientes).
NOT_FOUND404Ausência genérica. O cancelamento retorna este código, e não EXECUTION_NOT_FOUND, quando o id da execução não corresponde a nada.
EXECUTION_TERMINAL409Foi solicitado o cancelamento de uma execução já em estado terminal.
EXECUTION_TERMINATED409A execução principal chegou a um estado terminal (resend-representative-link).
NO_RESENDABLE_CHILD404Não há filho representante para reenviar (apenas em resend-representative-link).
LINK_NOT_FOUND404O token do link não existe.
LINK_EXPIRED410Token do link expirado.
FLOW_NOT_FOUND404Fluxo não encontrado.
RATE_LIMITED429Limite de requisições excedido.
PAYLOAD_TOO_LARGE413O corpo da requisição excedeu o limite global de 13 MiB, ou o limite da rota. A rota de envio de documento da jornada aceita um arquivo de até 8 MiB, cerca de 11,7 MiB de corpo depois da expansão do base64. Acima do limite global a conexão é recusada antes de chegar à API, portanto essa recusa não traz o envelope JSON de erro.
ROUTE_NOT_FOUND404O path não corresponde a nenhuma rota registrada.
METHOD_NOT_ALLOWED405O path existe, mas não aceita este verbo.
INTERNAL_ERROR500 / 502O fallback genérico para uma falha não mapeada, levantado na maioria dos endpoints. 500 vindo do tratador de erros compartilhado; 502 onde um handler encapsula uma chamada downstream, como faz o endpoint de sessões.
MétodoPathAuthDescrição
GET/api/v1/onboarding/flowsAPI Key ou JWTLista as jornadas disponíveis.
POST/api/v1/onboarding/sessionsAPI Key ou JWTCria uma sessão de onboarding (com sujeitos vinculados opcionais).
GET/api/v1/link/{token}Público (com limite de requisições)Resolve um token de link para o fluxo + payload do cliente. Sanitizado quando a sessão foi criada com surface_hint=embed.
GET/api/v1/onboarding/executions/{execution_id}API Key ou JWTConsulta a execução com os dados completos.
GET/api/v1/onboarding/executionsAPI Key ou JWTLista as execuções ativas.
GET/api/v1/executions/statsAPI Key ou JWTContagens agregadas por status, faixa de risco e tipo de fluxo, sobre todo o conjunto filtrado.
POST/api/v1/onboarding/executions/{execution_id}/resend-representative-linkJWT de Admin/SupervisorReenvia o link do representante pelos canais de notificação do fluxo.
POST/api/v1/executions/{execution_id}/cancelAPI Key ou JWTCancela uma execução (e o filho representante em kyc_minor), liberando o slot de sessão ativa para reuso.
GET/api/v1/onboarding/notificationsAPI Key ou JWTFaz polling das notificações de webhook pendentes.
POST/api/v1/onboarding/notifications/{delivery_id}/ackAPI Key ou JWTConfirma o processamento de uma notificação de webhook.

Versão 18, atualizada em