Referência da API CMP
Este documento descreve como integrar com a API da Guardline Case Management Platform (CMP). Ele cobre tokens de API, a abertura de casos em cada mesa de análise (alertas de fraude, solicitações de MED, alertas de PLD e casos de onboarding), idempotência e limite de requisições, como as decisões dos analistas retornam ao sistema de origem, webhooks de saída e tratamento de erros.
O modelo de integração segue uma separação clara de responsabilidades: o motor do integrador detecta e abre o caso; a Guardline enfileira, distribui, analisa e decide. Qualquer motor de decisão, interno ou de terceiros, pode abrir casos programaticamente. A CMP então aplica filas, SLAs, distribuição, cadeias de aprovação, sessões de comitê e a trilha de auditoria exatamente como faz para os casos gerados pelos demais módulos da Suíte Guardline.
Visão geral
Seção intitulada “Visão geral”A integração envolve quatro participantes:
| Participante | Responsabilidade |
|---|---|
| Sistema de origem | Detecta um evento (alerta de transação, solicitação de MED, alerta de PLD, derivação de onboarding) e abre um caso pela API de ingestão |
| API CMP da Guardline | Valida, deduplica, roteia o caso para uma mesa e uma fila, calcula o SLA e o distribui para um analista |
| Console CMP da Guardline | Console web onde analistas, supervisores e administradores trabalham os casos e gerenciam tokens |
| Receptor (opcional) | Endpoint do integrador que recebe webhooks de saída para eventos do workflow de PLD |
Cada mesa tem seu próprio endpoint de ingestão. Todos são POST, aceitam application/json e autenticam com um token de API com escopo restrito àquela mesa.
| Mesa | Endpoint | Escopo do token | Identificador do caso |
|---|---|---|---|
| Fraude | /api/v1/ingest/fraude | ingest:fraude | external_id |
| MED (Mecanismo Especial de Devolução) | /api/v1/ingest/med | ingest:med | id, event_id, notification_id ou med_id |
| PLD (prevenção à lavagem de dinheiro) | /api/v1/ingest/pld | ingest:pld | alerta_externo_id |
| Onboarding, pessoas físicas (KYC) e pessoas jurídicas (KYB) | /api/v1/ingest/kyc | ingest:kyc | execution_id (envelope de onboarding) ou case_id (payload genérico) |
Todas as demais rotas da API CMP (/api/v1/cases, /api/v1/queues, /api/v1/users e assim por diante) pertencem ao console e são autenticadas pelo cookie de sessão do analista. Essas rotas não fazem parte do contrato de integração e não estão documentadas aqui, com exceção dos endpoints de administração de tokens em Administração de tokens.
Todas as chamadas de API seguem o padrão REST sobre HTTPS com payloads JSON.
Ambientes e autenticação
Seção intitulada “Ambientes e autenticação”Ambientes
Seção intitulada “Ambientes”| Ambiente | URL base |
|---|---|
| Sandbox | https://{instance}.cmp.dev.guardline.com.br |
| Produção | https://{instance}.cmp.prod.guardline.com.br |
O valor {instance} é provisionado durante a configuração do integrador e identifica a infraestrutura dedicada. Cada instância tem seu próprio banco de dados e sua própria aplicação, isolados. Um token emitido em uma instância autentica apenas naquela instância.
Requisições HTTP são rejeitadas. HTTPS com TLS 1.2 ou superior é obrigatório em todos os ambientes.
Tokens de API
Seção intitulada “Tokens de API”Toda chamada de ingestão exige o cabeçalho X-API-Key com um token de API.
X-API-Key: cmp_e188905ec47a80ec035fe31a3264bf1a5f9231f1353ea24717cf311740305f11Os tokens têm o formato cmp_ seguido de 64 caracteres hexadecimais. São criados por um administrador da CMP, no console (tela de tokens de API) ou pelos endpoints de administração de tokens. O valor em texto puro é exibido apenas uma vez, na resposta de criação. Internamente, a CMP armazena o hash SHA-256 do token junto com um prefixo de exibição (os 12 primeiros caracteres, como cmp_e188905e), para que os administradores reconheçam um token sem expô-lo.
Cada token carrega:
| Atributo | Descrição |
|---|---|
label | Nome em texto livre, por exemplo fpp-producao ou onp-webhook-kyc |
scope | A mesa em que o token pode escrever. Consulte Escopos |
rate_limit_max, rate_limit_window_seconds | Limite de requisições por token. O padrão é 60 requisições a cada 60 segundos |
expires_in_days | Expiração opcional. 0 (o padrão) significa que o token nunca expira |
Um problema de credencial sempre responde 401 ou 403, nunca 400:
| Status | Mensagem | Causa |
|---|---|---|
| 401 | missing api key | O cabeçalho X-API-Key não foi enviado ou está vazio |
| 401 | invalid api key | O token é desconhecido, foi desativado, foi excluído ou expirou |
| 403 | token scope not authorized for this endpoint | O token existe, mas seu escopo não cobre o endpoint |
Um 400 significa que o token foi aceito e o corpo da requisição foi rejeitado, portanto reemitir o token não resolve o problema.
Toda chamada rejeitada (chave ausente, chave inválida, escopo incorreto, limite de requisições) é registrada na trilha de auditoria da CMP com o IP de origem, o caminho e o motivo. As ingestões bem-sucedidas também são registradas, com o corpo completo.
Escopos
Seção intitulada “Escopos”| Escopo | Endpoints permitidos |
|---|---|
ingest:fraude | POST /api/v1/ingest/fraude |
ingest:med | POST /api/v1/ingest/med |
ingest:pld | POST /api/v1/ingest/pld |
ingest:kyc | POST /api/v1/ingest/kyc (casos KYC e KYB compartilham este endpoint e este escopo) |
ingest | Os quatro endpoints de ingestão |
Use o escopo mais restrito que cubra o sistema de origem. O escopo genérico ingest existe para ferramentas e não deve ser emitido para um motor em produção que escreve em uma única mesa. Um token criado sem escopo explícito recebe o escopo genérico ingest, que é aceito nos quatro endpoints de mesa. As strings de escopo não são validadas na criação, portanto um erro de digitação gera um token que é rejeitado em todos os endpoints.
Limite de requisições
Seção intitulada “Limite de requisições”O limite de requisições é aplicado por token, por endpoint de mesa e por réplica da API, em uma janela fixa. O limite e a janela são atributos do token (rate_limit_max e rate_limit_window_seconds, com padrão de 60 requisições a cada 60 segundos) e são escolhidos pelo administrador no momento da criação. Cada rota de ingestão conta a própria janela e cada réplica mantém os próprios contadores em memória, portanto o teto efetivo é maior que o número configurado quando várias réplicas atendem ao tráfego. Janelas acima de 120 segundos não são confiáveis: uma janela é descartada dois minutos após o último uso, independentemente de rate_limit_window_seconds. Dimensione o limite para o pico do sistema de origem, lembrando que retries com backoff no lado do remetente podem gerar rajadas.
Quando o limite é excedido, a API responde 429 com a mensagem ingest rate limit exceeded. Aguarde o fim da janela e tente novamente; a requisição não foi processada.
O corpo da requisição é limitado a 1 MB por padrão, e o limite é configurável por instância. Um corpo acima do limite é rejeitado com 413. Decida os retries pelo status HTTP, e não pelo campo code: atualmente um corpo acima do limite informa code: INTERNAL_ERROR junto com o 413.
Cabeçalho de tenant
Seção intitulada “Cabeçalho de tenant”O cabeçalho opcional X-Tenant-Slug identifica o tenant do sistema de origem e é registrado na trilha de auditoria apenas nas chamadas rejeitadas, não em uma ingestão bem-sucedida. Quando omitido, assume o valor default. Ele não altera o roteamento: a instância é a fronteira de isolamento.
Formato da resposta
Seção intitulada “Formato da resposta”Os endpoints de ingestão retornam o corpo do recurso diretamente, sem envelope. O formato é específico de cada endpoint e está documentado em cada seção; as três mesas genéricas (fraude, MED, onboarding) compartilham este:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "external_id": "fpp-case-001", "created": true}Os erros usam um único envelope em todos os endpoints:
{ "error": true, "message": "external_id é obrigatório", "code": "BAD_REQUEST", "request_id": "5c1e8f2a-7b3d-4c9e-a1f0-2d6b8e4a9c7f"}| Campo | Descrição |
|---|---|
error | Sempre true em uma resposta de erro |
message | Motivo legível por humanos. As mensagens de validação estão em português ou em inglês, dependendo da mesa |
code | Código estável legível por máquina. Consulte Códigos de erro |
request_id | Identificador de correlação, também retornado no cabeçalho de resposta X-Request-ID. Informe-o ao acionar o suporte |
Fora de produção, o envelope também traz um campo details com o erro interno bruto. Em produção, o campo é omitido.
Idempotência
Seção intitulada “Idempotência”Todo endpoint de ingestão é idempotente sobre uma chave natural escolhida pelo sistema de origem, com escopo na mesa:
| Mesa | Chave natural |
|---|---|
| Fraude | external_id |
| MED | id, event_id, notification_id ou med_id (o primeiro presente); caso contrário, o SHA-256 do corpo bruto, armazenado e retornado como sha256: seguido do hash |
| PLD | alerta_externo_id |
| Onboarding | execution_id (envelope de onboarding) ou case_id (payload genérico de KYC) |
A primeira chamada para uma chave cria o caso e responde 201. Qualquer chamada posterior com a mesma chave responde 200 com created: false (ou is_new_case: false na mesa de PLD) e o identificador do caso existente. Nada é sobrescrito: o caso é imutável após a criação e o trabalho do analista nunca é substituído por um reenvio. Isso torna os endpoints seguros para repetir a chamada após um timeout de rede, sem gerar trabalho duplicado para a mesa.
A janela de idempotência é ilimitada: a chave permanece única durante todo o tempo de vida da mesa.
Alertas de fraude
Seção intitulada “Alertas de fraude”POST /api/v1/ingest/fraudeEscopo: ingest:fraude. Abre um caso na mesa de Fraude a partir de um alerta de transação produzido por um motor de detecção de fraude (a FPP da Guardline ou um motor de terceiros).
Campos da requisição
Seção intitulada “Campos da requisição”O corpo é um objeto JSON. Apenas três campos são obrigatórios; todo o restante enriquece o caso. Os nomes de chave alternativos aceitos para o mesmo campo estão listados na coluna de aliases. O corpo completo é preservado como payload do caso, portanto as chaves que não estão listadas aqui são mantidas e ficam visíveis para o analista, ainda que não sejam indexadas.
| Campo | Tipo | Obrigatório | Aliases | Descrição |
|---|---|---|---|---|
external_id | string | sim | id, event_id | Identificador único do alerta no sistema de origem. Chave de idempotência |
subject_document | string | sim | documento | CPF ou CNPJ do cliente investigado. Diferentemente da mesa de PLD, o formato não é validado: a pontuação não é removida e a quantidade de dígitos não é verificada, portanto o valor é armazenado exatamente como enviado e deve ser normalizado por quem chama |
transaction_amount | number | sim | amount, valor | Valor em BRL, deve ser maior que zero. Armazenado em centavos |
subject_name | string | não | cliente_nome, nome | Nome do cliente |
counterparty_name | string | não | contraparte_nome | Nome da contraparte (beneficiário) |
transaction_type | string | não | tipo_transacao | Tipo da transação, em minúsculas. O padrão é pix |
risk_level | string | não | risco | Apenas low, medium, high e critical são reconhecidos. Derivado de score quando ausente ou vazio; qualquer outro valor deixa o caso sem nível de risco |
score | integer | não | Score do motor na escala de 0 a 1000. Consulte Score, risco e prioridade | |
priority | integer | não | 0 a 9, quanto menor, mais urgente. O padrão é 3. Valores fora da faixa são ajustados ao limite | |
sla_deadline | string (RFC 3339) | não | Prazo de SLA explícito. Quando ausente, o prazo vem da política de SLA configurada para a mesa, e o caso é criado sem prazo quando nenhuma política se aplica. Um valor que não pode ser interpretado é ignorado silenciosamente e a política da mesa se aplica em seu lugar; nenhum 400 é retornado | |
fraud_analysis | object | não | Explicação completa da decisão e contexto do cliente. Consulte O bloco fraud_analysis |
Score, risco e prioridade
Seção intitulada “Score, risco e prioridade”A mesa de Fraude espera o score do motor na escala da FPP (0 a 1000). A CMP armazena score / 10, limitado a 100, no caso, para que filas e dashboards compartilhem uma única escala entre as mesas. A divisão é uma divisão inteira, portanto o resultado é truncado e não arredondado: 999 é armazenado como 99. O valor original permanece no payload do caso.
Quando risk_level está ausente ou vazio, ele é derivado do score bruto:
| Score (0 a 1000) | risk_level derivado |
|---|---|
| 950 ou mais | critical |
| 850 a 949 | high |
| 700 a 849 | medium |
| abaixo de 700 | low |
Quando nem risk_level nem score são enviados, o caso é criado com risk_level = medium.
A derivação ocorre apenas quando o campo está ausente ou vazio. Somente os quatro valores acima são reconhecidos, e um valor não vazio que não seja reconhecido pula a derivação e deixa o caso sem nenhum nível de risco, portanto envie um dos quatro valores ou omita o campo por completo.
priority ordena o caso na fila e alimenta o motor de distribuição junto com o score. É independente do nível de risco.
O bloco fraud_analysis
Seção intitulada “O bloco fraud_analysis”fraud_analysis é o que o painel de análise de fraude do analista renderiza. Ele é armazenado literalmente e fornece a explicação do workflow (estágios, políticas, regras e suas contribuições), o detalhe da transação com dispositivo e localização, o perfil do cliente com baselines comportamentais e transações recentes, comparações de anomalias e as linhas do tempo do cliente e da contraparte. Envie-o em todo alerta para que o painel reflita este caso.
{ "explanation": { "workflow_id": "wf-pix-out", "workflow_name": "pix_out_standard", "total_score": 780, "decision": "REVIEW", "review_threshold": 600, "block_threshold": 900, "stages_executed": 3, "stages_total": 3, "early_terminated": false, "execution_time_ms": 45, "stages_results": [ { "stage_id": "st-velocity", "stage_name": "velocity", "score": 320, "should_block": false, "policies_results": [ { "policy_id": "pol-velocity-amount", "policy_name": "amount_vs_baseline", "contribution": 200, "score_weight": 0.4, "rules_results": [ { "rule_id": "rule-amount-4x", "rule_name": "amount_over_4x_avg", "passed": false, "score": 300, "score_contribution": 150, "category": "velocity" } ] } ] } ] }, "transaction": { "id": "tx-9f1c", "amount": 35407.28, "currency": "BRL", "type": "TED", "channel": "internet_banking", "direction": "OUT", "account_id": "acc-1234", "counterpart_document": "04523967199", "counterpart_name": "Empresa Suspeita LTDA", "created_at": "2026-04-02T14:32:10Z", "device": { "type": "mobile", "os": "Android 14", "is_new": false, "is_vpn": false, "is_proxy": false }, "location": { "city": "São Paulo", "state": "SP", "country": "BR", "ip": "189.40.72.33" } }, "customer": { "profile": { "id": "cust-7788", "name": "Ana Sophia Lopes", "document_type": "CPF", "document_number": "61589720458", "customer_type": "person", "customer_since": "2019-03-15", "score": 100, "kyc_status": "approved", "risk_rating": "low", "is_pep": false, "sanctions_found": false, "previous_fraud_count": 0 }, "baselines": [ { "period_days": 90, "transaction_type": "ALL", "amount_avg": 7523.45, "amount_stddev": 10724.0, "amount_p95": 18000.0, "tx_count": 23, "unique_counterparts": 7, "typical_hours": [9, 10, 11, 14, 15, 16, 17], "typical_days": [1, 2, 3, 4, 5] } ], "recent_transactions": [] }, "comparisons": [ { "label": "Valor", "is_anomalous": true, "severity": "high", "value": "4.7x", "detail": "R$ 35.407,28 vs média R$ 7.523,45" } ], "network_stats": { "unique_payers": 8, "transaction_count": 12, "active_days": 45, "avg_transaction": 22500.0 }, "customer_timeline": { "account_id": "acc-1234", "transactions": [], "aggregates": [] }, "counterpart_timeline": { "counterpart_document": "04523967199", "transactions": [], "aggregates": [] }}Todos os sub-blocos são opcionais. Envie o que o motor tiver: um sub-bloco que não for enviado simplesmente não é renderizado, enquanto um sub-bloco cujos tipos de campo não correspondem ao schema é descartado, portanto respeite o formato documentado.
Exemplos
Seção intitulada “Exemplos”Alerta mínimo:
curl -X POST https://{instance}.cmp.prod.guardline.com.br/api/v1/ingest/fraude \ -H "Content-Type: application/json" \ -H "X-API-Key: cmp_..." \ -d '{ "external_id": "fpp-case-001", "subject_document": "12345678901", "transaction_amount": 15000.50 }'Alerta completo:
{ "external_id": "fpp-case-002", "subject_name": "Ana Sophia Lopes", "subject_document": "61589720458", "counterparty_name": "Empresa Suspeita LTDA", "transaction_type": "ted", "transaction_amount": 35407.28, "risk_level": "high", "priority": 2, "score": 810, "sla_deadline": "2026-04-03T14:32:10Z", "fraud_analysis": { "explanation": { "...": "..." }, "transaction": { "...": "..." }, "customer": { "...": "..." } }}Respostas
Seção intitulada “Respostas”201 Created, caso novo:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "external_id": "fpp-case-002", "created": true}200 OK, o external_id já existe na mesa de Fraude:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "external_id": "fpp-case-002", "created": false}O novo caso entra na mesa de Fraude com status new, status de negócio aguardando_analise e um item de trabalho enfileirado. O motor de distribuição o atribui a um analista disponível da fila de entrada.
| Status | message | Causa |
|---|---|---|
| 400 | empty body | Nenhum corpo foi enviado |
| 400 | invalid json | O corpo não é um JSON válido |
| 400 | external_id é obrigatório | Identificador ausente |
| 400 | subject_document é obrigatório | Documento do cliente ausente |
| 400 | transaction_amount deve ser maior que zero | Valor ausente, não numérico ou não positivo |
| 401 | missing api key, invalid api key | Consulte Tokens de API |
| 403 | token scope not authorized for this endpoint | O escopo do token não é ingest:fraude nem ingest |
| 429 | ingest rate limit exceeded | Limite por token excedido |
Solicitações de MED
Seção intitulada “Solicitações de MED”POST /api/v1/ingest/medEscopo: ingest:med. Abre um caso na mesa de MED a partir de uma solicitação do Mecanismo Especial de Devolução (MED) recebida pela instituição.
Campos da requisição
Seção intitulada “Campos da requisição”A mesa de MED foi a primeira mesa da plataforma e aceita o conjunto mais amplo de aliases de chave, para que exportações já existentes do backoffice de PIX possam ser encaminhadas com transformação mínima. O corpo completo é preservado como payload do caso.
| Campo | Tipo | Obrigatório | Aliases | Descrição |
|---|---|---|---|---|
valor_solicitado | number | sim | amount, valor | Valor solicitado para devolução, em BRL, maior que zero. Armazenado em centavos |
id | string ou number | recomendado | event_id, notification_id, med_id | Identificador da solicitação de MED no sistema de origem. Chave de idempotência. Quando todos estão ausentes, é usado o SHA-256 do corpo bruto, armazenado e retornado como sha256: seguido do hash, de modo que um reenvio idêntico byte a byte continua sendo deduplicado. O hash do corpo não sobrevive a nenhuma mudança na serialização (ordem das chaves, formatação dos números, um timestamp presente no payload), portanto ele não substitui o envio de um identificador real |
request_id | string | não | med_request_id, id | Número do protocolo MED exibido ao analista. Quando ausente, ele assume um valor derivado de uma cópia reserializada do payload, que não necessariamente coincide com o external_id retornado a quem chama, portanto envie-o explicitamente |
requested_at | string (RFC 3339) | não | created_at, event_at | Quando a solicitação foi aberta. O padrão é o horário da ingestão |
motivo | string | não | reason_code, reason, tipo | golpe (padrão), fraude ou erro_operacional. Valores desconhecidos assumem golpe |
status | string | não | med_status | aberta (padrão), em_analise, aguardando_instituicao, aprovado, negado ou recusado. Valores desconhecidos assumem aberta |
prazo_limite_at | string (RFC 3339) | não | deadline_at | Prazo regulatório de resposta da solicitação de MED |
recebedora | object | não | recipient, counterparty | Instituição recebedora: { "nome": "...", "ispb": "..." }. Em recebedora, apenas nome é lido; name funciona em recipient e counterparty. Chaves planas recebedora_nome, counterparty_name e recebedora_ispb também são aceitas. Quando nenhum nome de instituição recebedora pode ser resolvido, o caso é armazenado com um nome placeholder |
recebedora_respondeu | boolean | não | Indica se a instituição recebedora já respondeu | |
valor_devolvido | number | não | Valor já devolvido, em BRL | |
data_devolucao_at | string (RFC 3339) | não | Quando a devolução ocorreu | |
subject_name | string | não | cliente_nome, nome | Nome do cliente |
subject_document | string | não | cliente_documento, documento | CPF ou CNPJ do cliente |
transaction_type | string | não | tipo_transacao | O padrão é pix |
risk_level | string | não | risk | low, medium, high ou critical. Valores desconhecidos são ignorados |
score | integer | não | 0 a 100. Diferentemente da mesa de Fraude, o score da mesa de MED não é normalizado: ele já deve estar na escala de 0 a 100, e valores fora dessa faixa são rejeitados | |
priority | integer | não | 0 a 9, quanto menor, mais urgente. O padrão é 3 |
Exemplo
Seção intitulada “Exemplo”curl -X POST https://{instance}.cmp.prod.guardline.com.br/api/v1/ingest/med \ -H "Content-Type: application/json" \ -H "X-API-Key: cmp_..." \ -d '{ "id": "MED-2026-000123", "requested_at": "2026-08-20T13:05:00Z", "valor_solicitado": 4200.00, "motivo": "golpe", "status": "aberta", "prazo_limite_at": "2026-08-27T13:05:00Z", "subject_name": "Carlos Eduardo Nunes", "subject_document": "47055107806", "recebedora": { "nome": "Banco Exemplo S.A.", "ispb": "12345678" }, "priority": 2 }'Respostas
Seção intitulada “Respostas”201 Created quando o caso é novo, 200 OK com created: false quando o identificador já existe na mesa de MED:
{ "case_id": "8d1b3f4e-2a6c-4d7e-9b0a-1c2d3e4f5a6b", "external_id": "MED-2026-000123", "created": true}| Status | message | Causa |
|---|---|---|
| 400 | empty body, invalid json | Requisição malformada |
| 400 | missing or invalid valor_solicitado | Valor ausente, não numérico ou não maior que zero |
| 401 | missing api key, invalid api key | Consulte Tokens de API |
| 403 | token scope not authorized for this endpoint | O escopo do token não é ingest:med nem ingest |
| 429 | ingest rate limit exceeded | Limite por token excedido |
Alertas de PLD
Seção intitulada “Alertas de PLD”POST /api/v1/ingest/pldEscopo: ingest:pld. Abre ou enriquece um caso na mesa de PLD (prevenção à lavagem de dinheiro) a partir de um alerta transacional produzido por um motor de monitoramento. A mesa de PLD difere das demais em dois pontos: alertas de um cliente que já tem um caso aberto são consolidados nesse caso em vez de abrir um novo, e movimentações em espécie iguais ou superiores ao limiar regulatório são sinalizadas como COE automática (comunicação de operação em espécie), que pula a triagem.
Campos da requisição
Seção intitulada “Campos da requisição”| Campo | Tipo | Obrigatório | Aliases | Descrição |
|---|---|---|---|---|
alerta_externo_id | string | sim | Identificador único do alerta no sistema de origem. Chave de idempotência. Limitado a 100 caracteres; um valor mais longo atualmente falha como erro de servidor, e não como erro de validação, portanto respeite o limite | |
cpf_cnpj | string | sim | CPF (11 dígitos) ou CNPJ (14 dígitos) do cliente investigado. A pontuação é removida antes da validação | |
tipo_alerta | string | sim | Código do tipo de alerta, sem diferenciar maiúsculas de minúsculas. Veja Tipos de alerta | |
subject_name | string | recomendado | cliente_nome, nome | Nome do cliente |
score_risco | number | recomendado | score | Score de risco. São aceitas tanto a escala de 0 a 100 quanto a de 0 a 1000; valores acima de 100 são divididos por 10 |
valor_total | number | recomendado | valor | Valor total da movimentação, em BRL. Usado pela regra de COE |
valor_total_centavos | integer | não | valor_centavos | O mesmo valor em centavos. Tem precedência sobre valor_total quando ambos estão presentes |
tipologia_sugerida | string[] | não | Códigos de tipologia sugeridos pelo motor, por exemplo ["PIX001", "EST001"] | |
dados_cliente | object | não | Contexto livre do cliente (renda declarada, atividade, cidade, estado). Armazenado literalmente e exibido no dossiê | |
dados_transacionais | object | não | Contexto transacional livre (quantidade de operações, período, canais). Armazenado literalmente | |
contrapartes | array | não | Contrapartes envolvidas. Armazenadas literalmente | |
is_coe | boolean | não | Força a classificação de COE automática independentemente do tipo e do valor | |
priority | integer | não | 0 a 9, quanto menor, mais urgente. O padrão é 3. Uma COE automática sempre fica com prioridade 1 | |
fpp_source_url | string | não | Deep link de volta para o alerta no sistema de origem, exibido no dossiê |
Tipos de alerta
Seção intitulada “Tipos de alerta”tipo_alerta deve ser um dos códigos abaixo. Um código desconhecido é rejeitado com 404.
| Código | Descrição |
|---|---|
pix | Transferência PIX |
ted | Transferência TED |
doc | Transferência DOC |
boleto | Pagamento de boleto |
especie | Movimentação em espécie (cédulas). O único tipo elegível para a regra de COE automática |
cambio | Operação de câmbio |
cartao | Operação com cartão |
deposito | Depósito |
saque | Saque |
transferencia | Transferência genérica entre contas |
outro | Qualquer outra movimentação |
Pipeline de processamento
Seção intitulada “Pipeline de processamento”Cada alerta passa por quatro etapas em uma única transação:
- Idempotência. Se já existe um alerta com o mesmo
alerta_externo_id, a API responde 200 comis_new_case: falseeconsolidated_intopreenchido com o número do caso que contém o alerta original. Nenhum dado de caso ou de alerta é gravado; o reenvio é registrado na trilha de auditoria. Duas chamadas que carregam o mesmo identificador e chegam no mesmo instante são uma situação diferente: uma delas perde a corrida no índice único e recebe 409 com o códigoALREADY_EXISTS, o que pode ser tratado com segurança como uma duplicata. - Detecção de COE. O alerta é uma COE automática quando
is_coeétrue, ou quandotipo_alertaéespeciee o valor total é igual ou superior a R$ 50.000,00. Uma COE automática sempre cria um novo caso (nunca é consolidada), começa no status PLDpendente_geracao_xmlcom prioridade 1 e é projetada comodecided/aprovadono status genérico do caso, de modo que apareça como pronta para a etapa de comunicação ao COAF. - Consolidação. Quando o alerta não é uma COE e o mesmo
cpf_cnpjjá tem um caso de PLD ativo (qualquer status PLD diferente dearquivado_triagem,arquivado,comunicado_coaf,retificadooucancelado), o alerta é anexado a esse caso. A API responde 200 comis_new_case: falseeconsolidated_intopreenchido com o número do caso existente. O analista vê o novo alerta no dossiê do caso em que já está trabalhando. Um alerta de COE sempre abre o próprio caso e nunca é consolidado em um caso ativo existente. A COE também exige que o sistema de origem marque o alerta como operação em espécie (tipo_alertaigual aespecieou a flag explícitais_coe): o limiar de valor sozinho não a aciona. - Novo caso. Caso contrário, um novo caso de PLD é criado com status
new, status de negócioaguardando_analise, status PLDnovo, prazo de seleção de 45 dias (prazo_selecao) e um número de caso legível no formatoPLD-YYYY-NNNNN. A API responde 201.
Exemplos
Seção intitulada “Exemplos”Alerta mínimo:
{ "alerta_externo_id": "ALR-2026-000981", "cpf_cnpj": "12345678901", "tipo_alerta": "pix"}Alerta recomendado:
curl -X POST https://{instance}.cmp.prod.guardline.com.br/api/v1/ingest/pld \ -H "Content-Type: application/json" \ -H "X-API-Key: cmp_..." \ -d '{ "alerta_externo_id": "ALR-2026-000982", "cpf_cnpj": "321.987.654-00", "tipo_alerta": "pix", "subject_name": "Marcia Helena Souza", "score_risco": 82, "valor_total": 47500, "tipologia_sugerida": ["PIX001"], "dados_cliente": { "cidade": "São Paulo", "uf": "SP", "renda_declarada": 4500, "atividade": "Vendedora autônoma" }, "dados_transacionais": { "qtd_operacoes": 18, "periodo_dias": 5 }, "fpp_source_url": "https://fpp.example.com/alerts/ALR-2026-000982" }'Movimentação em espécie que dispara uma COE automática:
{ "alerta_externo_id": "ALR-2026-000990", "cpf_cnpj": "12345678000199", "tipo_alerta": "especie", "subject_name": "Acme Serviços LTDA", "valor_total_centavos": 7500000}Respostas
Seção intitulada “Respostas”201 Created, novo caso:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "case_pld_detail_id": "21be5573-402d-49d8-ab8d-461cb27f60b7", "numero_caso": "PLD-2026-00021", "is_new_case": true, "is_coe_automatica": false, "alerta_externo_id": "ALR-2026-000982"}200 OK, alerta duplicado ou alerta consolidado em um caso ativo do mesmo cliente:
{ "case_id": "5378d55e-f23f-4fd8-a2ce-1a28e9399535", "case_pld_detail_id": "b039a7cc-2a32-4dec-a6f2-5b7d643c82d3", "numero_caso": "PLD-2026-00006", "is_new_case": false, "is_coe_automatica": false, "consolidated_into": "PLD-2026-00006", "alerta_externo_id": "ALR-2026-000983"}| Campo | Descrição |
|---|---|
case_id | UUID do caso que contém o alerta |
case_pld_detail_id | UUID do registro de detalhe de PLD desse caso |
numero_caso | Número do caso legível, o identificador que os operadores citam |
is_new_case | true quando um caso foi criado por esta chamada |
is_coe_automatica | true quando o caso é uma COE automática. Preenchido apenas quando esta chamada cria ou consolida o caso: uma repetição idempotente simples retorna false mesmo para um caso de COE, portanto leia esse campo na resposta da criação, e não em uma repetição |
consolidated_into | Presente apenas quando is_new_case é false: o número do caso que absorveu o alerta, seja porque o alerta_externo_id já era conhecido, seja porque o cliente tinha um caso ativo |
alerta_externo_id | Eco do identificador da requisição |
Para distinguir as duas situações de 200, compare numero_caso com o que o sistema de origem esperava: uma duplicata retorna o caso do alerta original; uma consolidação retorna um caso que foi aberto por um alerta diferente.
| Status | message | Causa |
|---|---|---|
| 400 | empty body, invalid json | Requisição malformada |
| 400 | alerta_externo_id is required | Identificador ausente |
| 400 | cpf_cnpj must be a valid 11 or 14-digit document | Depois da remoção dos caracteres não numéricos, o documento não tem 11 nem 14 caracteres |
| 400 | tipo_alerta is required | Tipo de alerta ausente |
| 404 | pld_alert_type "xyz" not found | tipo_alerta não é um dos tipos de alerta; a mensagem cita o valor que foi enviado |
| 401 | missing api key, invalid api key | Veja Tokens de API |
| 403 | token scope not authorized for this endpoint | O escopo do token não é ingest:pld nem ingest |
| 429 | ingest rate limit exceeded | Limite por token excedido |
Casos de onboarding (KYC e KYB)
Seção intitulada “Casos de onboarding (KYC e KYB)”POST /api/v1/ingest/kycEscopo: ingest:kyc. Abre um caso nas mesas de onboarding: Cadastro PF (KYC, pessoas físicas) ou Cadastro PJ (KYB, empresas). Não existe uma rota /ingest/kyb separada; a mesa é escolhida pelo flow_type do payload.
O endpoint aceita dois formatos de corpo, diferenciados pelo campo event_type:
| Formato | Quando | Discriminador |
|---|---|---|
| Envelope de onboarding | O caso vem da ONP da Guardline ou de um motor de onboarding que segue o mesmo contrato. Suporta os fluxos KYC, KYC menor de idade, representante legal e KYB, e devolve as decisões terminais para reconciliação | event_type é cmp.case.review, cmp.case.approved ou cmp.case.rejected |
| Payload genérico de KYC | Um motor de decisão de terceiros envia sua análise completa de KYC de uma pessoa física | event_type ausente ou com qualquer outro valor |
Os dois formatos respondem com o mesmo corpo { case_id, external_id, created }.
Assinatura da requisição (HMAC)
Seção intitulada “Assinatura da requisição (HMAC)”Além do token de API, o endpoint pode exigir uma assinatura HMAC. A camada de assinatura é habilitada por instância, quando um segredo compartilhado é provisionado durante a configuração da integração; até lá, apenas o X-API-Key é exigido. Quando habilitada, a assinatura é verificada antes do token de API.
| Cabeçalho | Valor |
|---|---|
X-Guardline-Timestamp | Tempo Unix em segundos (inteiro) no momento da assinatura |
X-Guardline-Signature | HMAC-SHA256 de {timestamp}.{body} em hexadecimal minúsculo |
Procedimento de assinatura:
- Serialize o corpo uma única vez. Os bytes enviados devem ser exatamente os bytes assinados; não serialize novamente nem reformate o conteúdo depois de assinar.
- Concatene o timestamp, um ponto literal e o corpo bruto:
{timestamp}.{body}. - Calcule o HMAC-SHA256 dessa string com o segredo compartilhado e codifique o digest em hexadecimal.
BODY='{"event_type":"cmp.case.review","execution_id":"...","flow_type":"kyc", "...":"..."}'TS=$(date +%s)SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -X POST https://{instance}.cmp.prod.guardline.com.br/api/v1/ingest/kyc \ -H "Content-Type: application/json" \ -H "X-API-Key: $TOKEN" \ -H "X-Guardline-Timestamp: $TS" \ -H "X-Guardline-Signature: $SIG" \ --data-raw "$BODY"O timestamp deve estar dentro de 5 minutos do relógio do servidor (a tolerância é configurável por instância). Falhas de assinatura respondem 401 com um dos valores missing signature, missing timestamp, malformed timestamp, stale timestamp, malformed signature ou invalid signature.
Este é o mesmo formato que a ONP da Guardline usa nos seus webhooks de saída, portanto um integrador que já verifica os webhooks da ONP pode reaproveitar a rotina para assinar as chamadas à CMP.
Envelope de onboarding
Seção intitulada “Envelope de onboarding”O envelope é o corpo que a ONP da Guardline envia à CMP quando uma execução de onboarding é encaminhada para análise manual e, novamente, quando a execução chega a uma decisão terminal. A referência da API da ONP descreve o lado produtor; esta seção descreve o que a CMP espera e faz.
Semântica dos eventos
Seção intitulada “Semântica dos eventos”event_type | Efeito |
|---|---|
cmp.case.review | Cria o caso e o coloca na fila da mesa. Este é o único evento que cria um caso. Um reenvio para o mesmo execution_id responde 200 com created: false |
cmp.case.approved | Reconcilia uma aprovação terminal em um caso criado anteriormente por um review. Se o caso ainda não tem decisão, a aprovação é registrada como a decisão. Nunca cria um caso |
cmp.case.rejected | O mesmo que o anterior, para uma reprovação. O código de decisão kyc_reject_* (ou kyb_reject_*) é escolhido a partir de rejection_reasons |
Um evento terminal para um execution_id que nunca produziu um review responde 200 com case_id vazio e created: false: a execução foi decidida automaticamente pelo motor, nunca chegou à mesa e a CMP deliberadamente não cria um caso em fila para ela. Um evento terminal nunca sobrescreve uma decisão que um analista já registrou.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
event_type | string | sim | Veja Semântica dos eventos |
execution_id | string | sim | Identificador da execução de onboarding. Armazenado como o external_id do caso e usado como chave de idempotência |
flow_type | string | sim | kyc (pessoa física adulta), kyc_minor (menor de idade com representante legal), kyc_representative (a própria execução do representante, analisada como um caso à parte) ou kyb (empresa). kyb roteia para a mesa Cadastro PJ, os demais para Cadastro PF |
subject | object | sim | Veja abaixo |
case_envelope | object | sim | Veja abaixo |
event_id | string | não | Identificador do evento no lado produtor, armazenado junto com o caso |
fired_at | string (RFC 3339) | não | Quando o produtor emitiu o evento |
tenant_id | string | não | Tenant do produtor, informativo |
flow_id, flow_group_code | string | não | Fluxo e grupo de fluxo da execução, informativos |
edd_flagged | boolean | não | A execução exige diligência reforçada. Exibido ao analista |
rejection_reasons | string[] | não | Motivos em texto livre por trás de uma reprovação. Usados para classificar o código de decisão da reprovação |
derivation_reasons | object[] | não | Por que o motor encaminhou o caso para análise manual: { "code", "label_ptbr", "severity", "source", "detail" }. Exibido literalmente no dossiê |
representative | object | não | Somente kyc_minor: { "execution_id", "status" } da execução do representante legal |
guardian_type | string | não | Somente kyc_minor: parent, mother, father ou other |
representatives | object[] | não | Somente kyb: os representantes legais da empresa, { "execution_id", "status", "name", "flag", "document", "document_type" }. flag é true quando o representante foi reprovado ou expirou. Entradas sem execution_id ou sem name são descartadas |
subject:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
document | string | apenas em eventos terminais | CPF ou CNPJ, apenas dígitos (a pontuação é removida). Um cmp.case.review pode chegar com o documento vazio quando a execução foi encaminhada antes de a etapa de identidade ser concluída; o analista resolve a identidade na mesa |
document_type | string | não | cpf ou cnpj |
name | string | não | Nome do sujeito, exibido na fila |
email, phone, birth_date | string | não | Capturados no payload do caso |
case_envelope:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
priority | string | sim | P0, P1, P2 ou P3, quanto menor, mais urgente. Mapeado para a prioridade do caso de 0 a 3. Qualquer outro valor é rejeitado |
case_status | string | não | Status do motor: approved, rejected, blocked, escalated ou outro. Define a decisão de workflow exibida ao analista (veja o mapeamento abaixo) |
decision_code | string | não | Decisão do motor: approved, rejected ou pending_review (as formas curtas approve, reject e review também são aceitas). Define a decisão terminal da CMP em cmp.case.approved / cmp.case.rejected |
risk_level | string | não | low, medium, high ou critical, sem diferenciar maiúsculas e minúsculas. Valores desconhecidos são ignorados |
total_score | number | não | Score do motor. Armazenado como o score do workflow e, limitado a 0 a 100, como o score do caso usado pelas filas |
sla_deadline_at, completed_at, expires_at | string (RFC 3339) | não | Timestamps do produtor. completed_at passa a ser o momento da decisão em um evento terminal |
Mapeamento para o caso
Seção intitulada “Mapeamento para o caso”| Origem | Atributo do caso | Regra |
|---|---|---|
case_envelope.case_status | Decisão de workflow exibida na mesa | approved vira ALLOW; rejected e blocked viram BLOCK; qualquer outro valor vira REVIEW |
case_envelope.decision_code + rejection_reasons | Código de decisão terminal | approved registra kyc_approve_onboarding (kyb_approve_onboarding para empresas). rejected registra kyc_reject_fraud_suspicion quando os motivos mencionam fraud, suspicious ou scam; kyc_reject_document_invalid quando mencionam document, invalid_doc, expired ou illegible; kyc_reject_compliance nos demais casos. pending_review não registra nada: o analista decide |
case_envelope.priority | Prioridade do caso | P0 a P3 mapeiam para 0 a 3 |
case_envelope.risk_level | Nível de risco do caso | Convertido para minúsculas; valores desconhecidos deixam o nível de risco indefinido |
case_envelope.total_score | Score do caso | Arredondado e limitado a 0 a 100 |
subject.name, subject.document | Colunas da fila | Exibidas na listagem da mesa |
Os casos criados por um cmp.case.review entram na mesa com status new, status de negócio aguardando_analise e um item de trabalho em fila, exatamente como nas demais mesas. No momento da ingestão apenas o envelope é persistido; o dossiê completo (documentos, biometria, resultados dos parceiros, árvore de decisão) é buscado na ONP sob demanda quando o analista abre o caso.
Exemplos
Seção intitulada “Exemplos”Caso KYC encaminhado para análise manual:
{ "event_id": "01HZX5K7TPMJ9R2QXYZABCDEF1", "event_type": "cmp.case.review", "fired_at": "2026-05-25T17:00:00Z", "tenant_id": "default", "execution_id": "7b5c3d22-3e5f-4a7b-93b8-c6f5a2d4a4e2", "flow_id": "61000000-0000-0000-0000-000000000003", "flow_type": "kyc_minor", "flow_group_code": "Guardline KYC Menor", "case_envelope": { "case_status": "escalated", "decision_code": "pending_review", "risk_level": "medium", "total_score": 480, "priority": "P1", "sla_deadline_at": "2026-05-26T17:00:00Z", "completed_at": "2026-05-25T17:00:00Z", "expires_at": "2026-06-25T17:00:00Z" }, "subject": { "document": "11122233344", "document_type": "cpf", "name": "Pedro Henrique Silva", "email": "pedrinho@example.com", "phone": "+5511977665544", "birth_date": "2010-07-08" }, "representative": { "execution_id": "6c4d2e11-4a6b-5c8d-04c9-d7e6b3f5b5a3", "status": "completed" }, "rejection_reasons": [], "derivation_reasons": [ { "code": "biometrics_not_completed", "label_ptbr": "Biometria não concluída", "severity": "medium", "source": "onp" } ], "edd_flagged": false, "guardian_type": "mother"}Caso KYB encaminhado para análise manual:
{ "event_id": "6f7d1e10-2a3b-4c5d-8e9f-0a1b2c3d4e5f", "event_type": "cmp.case.review", "fired_at": "2026-07-17T17:00:00Z", "tenant_id": "default", "execution_id": "9a1f6c33-5d2e-4b8a-a7c1-2e3f4a5b6c7d", "flow_type": "kyb", "flow_group_code": "Guardline KYB", "case_envelope": { "case_status": "escalated", "decision_code": "pending_review", "risk_level": "high", "total_score": 720, "priority": "P1", "sla_deadline_at": "2026-07-18T17:00:00Z", "completed_at": "2026-07-17T17:00:00Z" }, "subject": { "document": "12345678000199", "document_type": "cnpj", "name": "Acme Serviços Financeiros LTDA" }, "representatives": [ { "execution_id": "0c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f", "status": "completed", "name": "Maria Oliveira", "flag": false, "document": "47055107806", "document_type": "cpf" }, { "execution_id": "1d2e3f4a-5b6c-7d8e-9f0a-1b2c3d4e5f6a", "status": "expired", "name": "João Pereira", "flag": true, "document": "61589720458", "document_type": "cpf" } ], "rejection_reasons": [], "derivation_reasons": [ { "code": "provider_unavailable", "label_ptbr": "Consulta de QSA indisponível", "severity": "medium", "source": "kyb_provider" } ], "edd_flagged": true}Reprovação terminal reconciliada no caso acima:
{ "event_type": "cmp.case.rejected", "execution_id": "9a1f6c33-5d2e-4b8a-a7c1-2e3f4a5b6c7d", "flow_type": "kyb", "subject": { "document": "12345678000199", "document_type": "cnpj" }, "case_envelope": { "case_status": "rejected", "decision_code": "rejected", "priority": "P1", "completed_at": "2026-07-18T09:12:00Z" }, "rejection_reasons": ["document_expired"]}Payload genérico de KYC
Seção intitulada “Payload genérico de KYC”Um motor de onboarding ou de decisão de terceiros que não implementa o envelope pode enviar sua análise completa de KYC de uma pessoa física. Três campos são obrigatórios; todo o restante é opcional e, quando presente, é indexado para que a mesa possa filtrar e exibir a informação. O corpo completo é preservado como o payload do caso. As chaves são aceitas em camelCase (como listadas) e, na maioria delas, também em snake_case.
Campos obrigatórios
Seção intitulada “Campos obrigatórios”| Campo | Tipo | Descrição |
|---|---|---|
case_id | string | Identificador único da análise no sistema de origem. Chave de idempotência. external_id e id são aceitos como aliases |
subject.personal.cpf | string | CPF do cliente, 11 dígitos sem pontuação. Também é lido de subject.basicData.cpf, subject.cpf.cpf ou de um subject_document no nível raiz |
decision.finalDecision | string | ALLOW, REVIEW ou BLOCK, sem diferenciar maiúsculas e minúsculas. decision.final_decision é aceito |
Blocos opcionais
Seção intitulada “Blocos opcionais”| Bloco | Campos lidos pela CMP |
|---|---|
subject.personal | name (também subject.basicData.name, subject.cpf.name, subject_name no nível raiz) |
decision | finalScore (0 a 100), thresholds.review, thresholds.block, workflowExplanation.{workflowId, workflowName, totalScore, reviewThreshold, blockThreshold, executionTimeMs, executedAt, stagesResults[]} |
trust | riskScore, riskLabel |
facematch | matched, confidenceScore |
liveness | mode |
facial | riskScore, riskLevel |
documentoscopia | qualityScore, confidenceScore, isExpired |
compliance | isPep, sanctions[], lawsuits[], govDebts[], mediaExposureUrls[] |
contact | mediaExposureUrls[] |
device | riskScore, isVpn, isEmulator, isRooted |
financial.financialRisk | risk_level, financial_risk_score, is_on_collection |
payload_version | Rótulo da versão do contrato, v1 por padrão |
Atributos derivados
Seção intitulada “Atributos derivados”A CMP deriva a prioridade e o nível de risco do caso a partir da decisão e dos sinais de compliance, biometria e dispositivo:
| Atributo | Regra |
|---|---|
| Prioridade (quanto menor, mais urgente) | Valor base por decisão: BLOCK 1, REVIEW 3, ALLOW 5. Qualquer ocorrência em listas de sanções força 0. Um PEP eleva para no máximo 1. Processos judiciais, débitos com órgãos públicos, facial.riskLevel = high ou um dispositivo marcado como VPN, emulador ou com root elevam para no máximo 2 |
| Nível de risco | BLOCK é critical. REVIEW é high quando o score final é pelo menos 90% do limiar de bloqueio, caso contrário medium. ALLOW é low. Sanções ou PEP elevam low e medium para high |
| Score | decision.finalScore (ou workflowExplanation.totalScore), limitado a 0 a 100 |
Exemplo
Seção intitulada “Exemplo”{ "case_id": "kyc-2026-000451", "subject": { "personal": { "cpf": "11122233344", "name": "Roberto Alves Lima" } }, "decision": { "finalDecision": "REVIEW", "finalScore": 65, "thresholds": { "review": 50, "block": 80 }, "workflowExplanation": { "workflowId": "wf-kyc-standard", "workflowName": "kyc_standard", "totalScore": 65, "reviewThreshold": 50, "blockThreshold": 80, "executionTimeMs": 1830, "executedAt": "2026-08-20T10:15:00Z" } }, "compliance": { "isPep": true, "sanctions": [], "lawsuits": [{ "court": "TJSP", "number": "1001234-56.2024.8.26.0100", "type": "civil" }], "govDebts": [] }, "facematch": { "matched": true, "confidenceScore": 88.5 }, "liveness": { "mode": "active" }, "facial": { "riskScore": 20, "riskLevel": "low" }, "documentoscopia": { "qualityScore": 92, "confidenceScore": 95, "isExpired": false }, "device": { "riskScore": 10, "isVpn": false, "isEmulator": false, "isRooted": false }, "financial": { "financialRisk": { "risk_level": "medium", "financial_risk_score": 55, "is_on_collection": false } }}Este payload chega à mesa Cadastro PF com prioridade 1 (PEP) e nível de risco high (o PEP eleva medium para high).
Respostas
Seção intitulada “Respostas”201 Created quando o caso é novo, 200 OK com created: false em um reenvio ou em uma reconciliação terminal:
{ "case_id": "9e1c8b3a-1234-5678-9abc-def012345678", "external_id": "7b5c3d22-3e5f-4a7b-93b8-c6f5a2d4a4e2", "created": true}| Status | message | Causa |
|---|---|---|
| 400 | empty body, invalid json | Requisição malformada |
| 400 | execution_id e obrigatorio | Envelope sem execution_id |
| 400 | flow_type "x" invalido (esperado kyc, kyb, kyc_minor ou kyc_representative) | flow_type desconhecido |
| 400 | subject e obrigatorio, case_envelope e obrigatorio | Objetos do envelope ausentes |
| 400 | subject.document e obrigatorio | Evento terminal sem documento |
| 400 | case_envelope.priority "x" invalida (esperado P0, P1, P2 ou P3) | Prioridade fora do conjunto aceito |
| 400 | case_id e obrigatorio | Payload genérico sem identificador |
| 400 | subject.personal.cpf e obrigatorio | Payload genérico sem CPF |
| 400 | decision.finalDecision deve ser ALLOW, REVIEW ou BLOCK | Payload genérico com uma decisão inválida |
| 401 | missing signature, invalid signature, stale timestamp e relacionados | Camada HMAC habilitada e a assinatura falhou |
| 401 | missing api key, invalid api key | Veja Tokens de API |
| 403 | token scope not authorized for this endpoint | O escopo do token não é ingest:kyc nem ingest |
| 429 | ingest rate limit exceeded | Limite por token excedido |
Ciclo de vida do caso
Seção intitulada “Ciclo de vida do caso”Mesas e filas
Seção intitulada “Mesas e filas”Os casos vivem em mesas (operações). Cada mesa tem uma fila de entrada, onde os casos novos chegam, e uma fila de supervisão para escalações. O código da mesa é o identificador estável usado pela API, por exemplo na requisição de transferência entre mesas; os nomes de exibição das filas são definidos no momento do deploy e podem ser renomeados por um administrador.
| Código da mesa | Mesa | Tipo de caso | Endpoint de ingestão |
|---|---|---|---|
med | MED | med | /api/v1/ingest/med |
fraude | Fraude | fraude | /api/v1/ingest/fraude |
kyc | Cadastro PF | kyc | /api/v1/ingest/kyc |
kyb | Cadastro PJ | kyb | /api/v1/ingest/kyc |
pld | PLD | pld | /api/v1/ingest/pld |
comercial | Comercial | mantém o tipo da mesa de origem | nenhum, recebe casos por transferência |
Todo caso carrega um status genérico, compartilhado por todas as mesas, e um status de negócio que o refina:
| Status genérico | Significado |
|---|---|
new | Criado pela ingestão, aguardando distribuição ou que um analista o puxe |
in_analysis | Atribuído a um analista |
escalated | Enviado para a fila de supervisão |
decided | Uma decisão foi registrada |
closed | Encerrado |
failed | A ocorrência não pôde ser aberta ou processada; o caso é liberado para reprocessamento |
A mesa PLD acrescenta o seu próprio workflow sobre esse conjunto (novo, em_triagem, em_investigacao, aguardando_revisao_investigacao, aguardando_comite, pendente_geracao_xml, xml_gerado, comunicado_coaf, erro_siscoaf, retificado, cancelado, arquivado, entre outros). As suas transições são as publicadas aos receptores como webhooks de saída.
Os prazos de SLA são calculados na ingestão a partir da política de SLA da mesa e do status (configurável por mesa, fila e status, com um limiar de alerta antes do vencimento). A mesa Fraude também aceita um sla_deadline explícito no payload.
Transferências entre mesas
Seção intitulada “Transferências entre mesas”Um analista pode entregar um caso a outra mesa quando a análise revela uma natureza diferente (suspeita de fraude em um caso de onboarding, indícios de lavagem de dinheiro, necessidade de um parecer comercial). O caso mantém o seu tipo e o seu histórico, e retorna pelo mesmo caminho. Pares habilitados hoje:
| De | Para |
|---|---|
Cadastro PF (kyc) | Fraude, PLD, Comercial (e de volta) |
Cadastro PJ (kyb) | Fraude, PLD, Comercial (e de volta) |
As transferências são registradas na trilha de auditoria com origem, destino, ator e justificativa. Elas não ficam visíveis ao sistema de origem pela API; a decisão que acaba encerrando o caso, sim.
Retorno da decisão
Seção intitulada “Retorno da decisão”A forma como o sistema de origem toma conhecimento da decisão do analista depende da mesa.
Casos de onboarding
Seção intitulada “Casos de onboarding”Para casos abertos pelo envelope de onboarding, a CMP envia a decisão do analista (aprovar, reprovar) de volta para a ONP da Guardline assim que ela é registrada, referenciando o execution_id. Apenas essas decisões terminais são propagadas: decisões intermediárias, como uma escalação ou uma solicitação de informações adicionais, permanecem na CMP e não geram evento na ONP. A ONP então retoma a execução e emite seus próprios eventos de onboarding para o integrador. Um integrador que usa ONP e CMP em conjunto consome, portanto, um único fluxo de webhooks, o descrito na referência da API da ONP, e não precisa fazer polling na CMP.
O retorno é entregue pelo mesmo outbox durável dos webhooks de saída descritos abaixo. Durante uma indisponibilidade temporária da ONP, ele é retentado um número pequeno de vezes ao longo de aproximadamente dois minutos e depois é marcado como esgotado; não há redrive automático depois disso, portanto recuperar a notificação exige um reenfileiramento manual pela operação da Guardline.
Casos de PLD: webhooks de saída
Seção intitulada “Casos de PLD: webhooks de saída”A mesa de PLD publica os marcos do seu workflow em receptores HTTP configurados para a instância. Um receptor é definido por um nome, uma URL de destino, um segredo HMAC e a lista de eventos que ele assina, e é provisionado pela Guardline durante a configuração da integração. Vários receptores podem assinar o mesmo evento.
Eventos
Seção intitulada “Eventos”| Evento | Emitido quando | pld_status resultante |
|---|---|---|
triagem_concluida | O alerta foi arquivado na triagem, após a investigação, ou pelo diretor ao arquivar um caso reprovado pelo comitê. Nesse terceiro caso a entrega carrega action: "pld_arquivar_reprovado" e chega logo após comite_reprovado, portanto convém rotear por payload.action e não apenas pelo nome do evento | arquivado_triagem ou arquivado |
comite_aprovado | A sessão do comitê foi encerrada com aprovação da comunicação | aprovado_comite |
comite_reprovado | A sessão do comitê foi encerrada reprovando a comunicação | reprovado_comite |
coaf_registrado | A comunicação ao COAF foi registrada (protocolo SISCOAF recebido) | comunicado_coaf |
coaf_erro | O envio ao SISCOAF falhou e o erro foi registrado | erro_siscoaf |
coaf_retificado | A comunicação foi retificada | retificado |
coaf_cancelado | A comunicação foi cancelada | cancelado |
Números de protocolo, hashes e recibos da comunicação ao COAF não são transportados no webhook, propositalmente. Eles ficam disponíveis para usuários autorizados na mesa.
Payload
Seção intitulada “Payload”Toda entrega envia o mesmo envelope; o bloco payload é estável entre os eventos de PLD, portanto um único decodificador atende a todos eles.
{ "event": "coaf_registrado", "delivery_id": "3f0c9b1e-8d2a-4e6f-b5c7-1a2b3c4d5e6f", "correlation_key": "PLD-2026-00042", "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "attempt": 1, "timestamp": "2026-08-20T14:05:11Z", "payload": { "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "numero_caso": "PLD-2026-00042", "action": "pld_registrar_coaf", "pld_status": "comunicado_coaf", "occurred_at": "2026-08-20T14:05:10Z" }}| Campo | Descrição |
|---|---|
event | Nome do evento da tabela acima |
delivery_id | Identificador estável desta entrega, idêntico em todas as retentativas. Use-o para deduplicação |
correlation_key | O número do caso de PLD |
case_id | UUID do caso |
attempt | 1 para a primeira entrega, 2 e 3 para as retentativas |
timestamp | Quando esta tentativa foi enviada |
payload.action | A ação do workflow que produziu a transição (por exemplo pld_arquivar_triagem, pld_registrar_coaf, pld_retificar, comite_aprovado) |
payload.pld_status | O status de PLD resultante |
payload.motivo | Presente apenas em coaf_retificado e coaf_cancelado: a justificativa informada pelo operador |
payload.occurred_at | Quando a transição foi efetivada |
Cabeçalhos
Seção intitulada “Cabeçalhos”| Cabeçalho | Descrição |
|---|---|
Content-Type | Sempre application/json |
X-Webhook-Event | O nome do evento, para roteamento sem fazer o parsing do corpo |
X-Webhook-Delivery | O identificador da entrega, mesmo valor de delivery_id no corpo |
X-Webhook-Signature | HMAC-SHA256 do corpo bruto, em hexadecimal |
Verificação da assinatura
Seção intitulada “Verificação da assinatura”A assinatura cobre apenas o corpo bruto da requisição (não há componente de timestamp, diferentemente da assinatura de entrada do onboarding).
- Leia os bytes do corpo bruto, antes de qualquer parsing de JSON.
- Calcule o HMAC-SHA256 do corpo com o segredo do receptor e codifique o resultado em hexadecimal.
- Compare com
X-Webhook-Signatureusando uma comparação em tempo constante.
import hmac, hashlib
def verify_cmp_webhook(body: bytes, signature: str, secret: str) -> bool: expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature)Política de retentativas
Seção intitulada “Política de retentativas”Uma entrega é bem-sucedida quando o receptor responde qualquer status 2xx em até 10 segundos. Caso contrário, a CMP faz novas tentativas:
| Tentativa | Espera após a falha anterior |
|---|---|
| 2 | Na próxima drenagem do outbox após a falha |
| 3 | Pelo menos 30 segundos depois disso |
O outbox é drenado por um job que roda a cada 60 segundos, portanto o intervalo de drenagem domina o tempo real e toda a janela de retentativas fica em torno de dois minutos. Após a terceira falha, a entrega é marcada como exhausted e não é mais reenviada automaticamente. As entregas são persistidas em um outbox antes da primeira tentativa, portanto um reinício da CMP nunca perde um evento; a garantia vale a partir do momento em que o registro da entrega é gravado. Reenfileirar uma entrega esgotada é uma operação manual executada pela operação da Guardline mediante solicitação, e não uma API de autoatendimento.
Configuração do endpoint
Seção intitulada “Configuração do endpoint”| Requisito | Detalhe |
|---|---|
| Protocolo | HTTPS obrigatório |
| Versão do TLS | 1.2 ou superior |
| Resposta de sucesso | Qualquer código 2xx, corpo ignorado |
| Timeout | Responder em até 10 segundos |
| Idempotência | Deduplicar por X-Webhook-Delivery (ou delivery_id) |
Receba o webhook, persista-o ou enfileire-o e responda 200 imediatamente. Faça o processamento de forma assíncrona.
Casos de fraude e MED
Seção intitulada “Casos de fraude e MED”As decisões nas mesas de Fraude e MED são registradas na CMP com sua justificativa, código de decisão e efeitos operacionais (bloqueio cautelar, bloqueio preventivo de conta, inclusões em listas) e ficam visíveis no detalhe do caso e na trilha de auditoria do console. O pipeline de webhooks de saída é compartilhado por todas as mesas, mas nenhum evento é publicado para essas mesas no momento; quando a instituição precisa que o resultado de fraude ou MED seja enviado ao seu sistema core, a assinatura é habilitada como parte da configuração da integração.
Administração de tokens
Seção intitulada “Administração de tokens”Os tokens são gerenciados por administradores da CMP. Os endpoints abaixo exigem o cookie de sessão do console de um usuário com o papel admin, que é herdado pelo papel de diretor de PLD, de modo que um diretor de PLD também pode gerenciar tokens; eles não podem ser chamados com um token de API. As mesmas operações estão disponíveis no console.
Listagem de tokens
Seção intitulada “Listagem de tokens”GET /api/v1/api-tokensRetorna todos os tokens com seu rótulo, prefixo, escopo, flag de atividade, limite de requisições, expiração, contador de uso e último uso. Os valores dos tokens nunca são retornados.
Criação de token
Seção intitulada “Criação de token”POST /api/v1/api-tokens{ "label": "fpp-producao", "scope": "ingest:fraude", "rate_limit_max": 300, "rate_limit_window_seconds": 60, "expires_in_days": 365}| Campo | Obrigatório | Descrição |
|---|---|---|
label | sim | Nome de exibição |
scope | não | Um dos escopos. Padrão: ingest |
rate_limit_max | não | Requisições por janela. Padrão: 60 |
rate_limit_window_seconds | não | Duração da janela. Padrão: 60 |
expires_in_days | não | Dias até a expiração. 0 ou ausente significa sem expiração |
Resposta 201:
{ "ok": true, "id": "c9a1e845-a565-43b6-ac29-2d3072b027b9", "token": "cmp_e188905ec47a80ec035fe31a3264bf1a5f9231f1353ea24717cf311740305f11", "token_prefix": "cmp_e188905e", "label": "fpp-producao", "scope": "ingest:fraude"}O campo token aparece apenas nesta resposta. Armazene-o imediatamente no cofre de segredos do sistema de origem; ele não pode ser recuperado depois. Se ele for perdido, crie um novo token e faça a rotação.
Rotação, desativação e exclusão
Seção intitulada “Rotação, desativação e exclusão”Rotação sem downtime: crie o novo token, configure-o no sistema de origem, confirme um ingest bem-sucedido e então desative o antigo.
PATCH /api/v1/api-tokens/{id}/toggle{ "is_active": false }A desativação mantém o registro do token e seu histórico de uso para auditoria. Um token desativado responde 401 invalid api key e pode ser reativado com is_active: true.
DELETE /api/v1/api-tokens/{id}A exclusão é permanente.
Catálogo de webhooks de entrada
Seção intitulada “Catálogo de webhooks de entrada”GET /api/v1/settings/webhooksRetorna o catálogo de todos os endpoints de entrada que a instância expõe (as quatro rotas de ingest mais os callbacks dos provedores de comunicação para WhatsApp, SMS e voz), com o caminho, os métodos aceitos, o tipo de autenticação e o cabeçalho, e o escopo de token exigido. O catálogo é derivado da tabela de rotas da versão em execução, portanto é a forma mais rápida de um administrador confirmar a URL exata a ser informada a um sistema de origem.
Diagramas de fluxo completos
Seção intitulada “Diagramas de fluxo completos”Alerta de fraude de um motor externo
Seção intitulada “Alerta de fraude de um motor externo”Caso de onboarding derivado pela ONP
Seção intitulada “Caso de onboarding derivado pela ONP”Alerta de PLD com comunicação ao COAF
Seção intitulada “Alerta de PLD com comunicação ao COAF”Referência de erros
Seção intitulada “Referência de erros”Códigos de status HTTP
Seção intitulada “Códigos de status HTTP”| Status | Significado |
|---|---|
| 200 | Requisição processada; o caso já existia (replay idempotente, consolidação ou reconciliação) |
| 201 | Caso criado |
| 400 | Corpo malformado ou validação falhou. Corrija a requisição antes de tentar novamente |
| 401 | Token de API ausente ou inválido, ou assinatura HMAC inválida |
| 403 | O escopo do token não cobre o endpoint |
| 404 | Um código referenciado não existe (por exemplo, um tipo_alerta de PLD desconhecido) |
| 409 | Conflito com o estado atual do caso |
| 429 | Limite de requisições excedido. Tente novamente após a janela |
| 500 | Erro interno. Tente novamente com backoff e informe o request_id ao suporte |
| 503 | Uma dependência está indisponível. Tente novamente com backoff |
Códigos de erro
Seção intitulada “Códigos de erro”code | Status típico | Descrição |
|---|---|---|
BAD_REQUEST | 400 | Um campo falhou na validação; message indica qual |
VALIDATION_ERROR | 400 | O corpo está vazio ou não é um JSON válido |
UNAUTHORIZED | 401 | Problema de credencial |
FORBIDDEN | 403 | Problema de escopo |
NOT_FOUND | 404 | Código de referência desconhecido |
CONFLICT | 409 | Conflito de estado |
DUPLICATE_EXTERNAL_ID | 409 | O identificador de ingestão já havia sido usado nessa mesa |
ALREADY_EXISTS | 409 | A mesa PLD já possui um alerta com esse alerta_externo_id |
RATE_LIMITED | 429 | Limite de requisições excedido |
SERVICE_UNAVAILABLE | 503 | Dependência indisponível |
INTERNAL_ERROR | 500 | Falha inesperada |
Tratamento recomendado: trate 400, 401, 403 e 404 como permanentes para aquela requisição (alerte o operador, não faça novas tentativas cegamente); trate 429, 500 e 503 como transitórios (faça novas tentativas com backoff exponencial, as chaves de idempotência tornam as retentativas seguras).
Resumo dos endpoints
Seção intitulada “Resumo dos endpoints”| Método | Caminho | Autenticação | Descrição |
|---|---|---|---|
| POST | /api/v1/ingest/fraude | Token de API (ingest:fraude) | Abre um caso na mesa de Fraude |
| POST | /api/v1/ingest/med | Token de API (ingest:med) | Abre um caso na mesa de MED |
| POST | /api/v1/ingest/pld | Token de API (ingest:pld) | Abre ou consolida um caso na mesa de PLD |
| POST | /api/v1/ingest/kyc | Token de API (ingest:kyc) + HMAC opcional | Abre ou reconcilia um caso na mesa Cadastro PF ou Cadastro PJ |
| GET | /api/v1/api-tokens | Sessão de administrador | Lista os tokens de API |
| POST | /api/v1/api-tokens | Sessão de administrador | Cria um token de API |
| PATCH | /api/v1/api-tokens/{id}/toggle | Sessão de administrador | Ativa ou desativa um token |
| DELETE | /api/v1/api-tokens/{id} | Sessão de administrador | Exclui um token |
| GET | /api/v1/settings/webhooks | Sessão de administrador | Catálogo dos endpoints de entrada |
| GET | /healthz, /readyz, /livez | Nenhuma | Probes de liveness e readiness |
Versão 18, atualizada em