CMP API Reference
This document describes how to integrate with the Guardline Case Management Platform (CMP) API. It covers API tokens, opening cases on each analysis desk (fraud alerts, MED requests, AML alerts and onboarding cases), idempotency and rate limiting, how analyst decisions flow back to the originating system, outbound webhooks, and error handling.
The integration model follows a clear separation of responsibilities: the integrator’s engine detects and opens the case, Guardline queues, distributes, analyzes and decides. Any decision engine, internal or third party, can open cases programmatically. The CMP then applies queues, SLAs, distribution, approval chains, committee sessions and the audit trail exactly as it does for cases generated by the other modules of the Guardline Suite.
Overview
Section titled “Overview”The integration involves four participants:
| Participant | Responsibility |
|---|---|
| Source system | Detects an event (transaction alert, MED request, AML alert, onboarding derivation) and opens a case through the ingest API |
| Guardline CMP API | Validates, deduplicates, routes the case to a desk and queue, computes SLA and distributes it to an analyst |
| Guardline CMP console | Web console where analysts, supervisors and administrators work the cases and manage tokens |
| Receiver (optional) | Integrator endpoint that receives outbound webhooks for AML workflow events |
Each desk has its own ingest endpoint. All of them are POST, accept application/json and authenticate with an API token scoped to that desk.
| Desk | Endpoint | Token scope | Case identifier |
|---|---|---|---|
| Fraud (Fraude) | /api/v1/ingest/fraude | ingest:fraude | external_id |
| MED (special refund mechanism) | /api/v1/ingest/med | ingest:med | id, event_id, notification_id or med_id |
| AML (anti money laundering) | /api/v1/ingest/pld | ingest:pld | alerta_externo_id |
| Onboarding, individuals (KYC) and companies (KYB) | /api/v1/ingest/kyc | ingest:kyc | execution_id (onboarding envelope) or case_id (generic payload) |
Every other route of the CMP API (/api/v1/cases, /api/v1/queues, /api/v1/users and so on) belongs to the console and is authenticated by the analyst’s session cookie. Those routes are not part of the integration contract and are not documented here, except for the token administration endpoints in Token Administration.
All API calls follow the REST pattern over HTTPS with JSON payloads.
Environments and Authentication
Section titled “Environments and Authentication”Environments
Section titled “Environments”| Environment | Base URL |
|---|---|
| Sandbox | https://{instance}.cmp.dev.guardline.com.br |
| Production | https://{instance}.cmp.prod.guardline.com.br |
The {instance} value is provisioned during integrator setup and identifies the dedicated infrastructure. Each instance has its own isolated database and application. A token issued on one instance authenticates only against that instance.
HTTP requests are rejected. HTTPS with TLS 1.2 or higher is required in all environments.
API Tokens
Section titled “API Tokens”Every ingest call requires the X-API-Key header carrying an API token.
X-API-Key: cmp_e188905ec47a80ec035fe31a3264bf1a5f9231f1353ea24717cf311740305f11Tokens have the shape cmp_ followed by 64 hexadecimal characters. They are created by a CMP administrator, either in the console (API tokens screen) or through the token administration endpoints. The plaintext value is displayed only once, in the creation response. Internally the CMP stores the SHA-256 hash of the token together with a display prefix (the first 12 characters, such as cmp_e188905e) so administrators can recognize a token without exposing it.
Each token carries:
| Attribute | Description |
|---|---|
label | Free text name, for example fpp-producao or onp-webhook-kyc |
scope | The desk the token may write to. See Scopes |
rate_limit_max, rate_limit_window_seconds | Per token rate limit. Defaults to 60 requests per 60 seconds |
expires_in_days | Optional expiry. 0 (the default) means the token never expires |
A credential problem always answers 401 or 403, never 400:
| Status | Message | Cause |
|---|---|---|
| 401 | missing api key | The X-API-Key header was not sent or is empty |
| 401 | invalid api key | The token is unknown, was deactivated, was deleted or has expired |
| 403 | token scope not authorized for this endpoint | The token exists but its scope does not cover the endpoint |
A 400 means the token was accepted and the request body was rejected, so reissuing the token will not resolve it.
Every rejected call (missing key, invalid key, wrong scope, rate limit) is recorded in the CMP audit trail with the source IP, path and reason. Successful ingests are recorded too, with the full body.
Scopes
Section titled “Scopes”| Scope | Endpoints allowed |
|---|---|
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 (KYC and KYB cases share this endpoint and scope) |
ingest | All four ingest endpoints |
Use the narrowest scope that covers the source system. The generic ingest scope exists for tooling and should not be issued to a production engine that writes to a single desk. A token created without an explicit scope receives the generic ingest scope, which is accepted on all four desk endpoints. Scope strings are not validated at creation, so a typo produces a token that is rejected on every endpoint.
Rate Limiting
Section titled “Rate Limiting”Rate limiting is applied per token, per desk endpoint and per API replica, in a fixed window. The limit and the window are attributes of the token (rate_limit_max and rate_limit_window_seconds, defaults of 60 requests per 60 seconds) and are chosen by the administrator at creation time. Each ingest route counts its own window and each replica keeps its own counters in memory, so the effective ceiling is higher than the configured number when several replicas serve traffic. Windows above 120 seconds are not reliable: a window is discarded two minutes after its last use, regardless of rate_limit_window_seconds. Size the limit for the peak of the source system, keeping in mind that retries with backoff on the sender side can produce bursts.
When the limit is exceeded the API answers 429 with the message ingest rate limit exceeded. Wait for the window to elapse and retry; the request was not processed.
The request body is limited to 1 MB by default, and the limit is configurable per instance. A body above the limit is rejected with 413. Decide retries from the HTTP status rather than from the code field: an oversized body currently reports code: INTERNAL_ERROR alongside the 413.
Tenant Header
Section titled “Tenant Header”The optional X-Tenant-Slug header identifies the tenant of the source system and is recorded in the audit trail only on rejected calls, not on a successful ingest. When omitted it defaults to default. It does not change routing: the instance is the isolation boundary.
Response Format
Section titled “Response Format”Ingest endpoints return the resource body directly, without an envelope. The shape is endpoint specific and documented in each section; the three generic desks (fraud, MED, onboarding) share this one:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "external_id": "fpp-case-001", "created": true}Errors use a single envelope on every endpoint:
{ "error": true, "message": "external_id é obrigatório", "code": "BAD_REQUEST", "request_id": "5c1e8f2a-7b3d-4c9e-a1f0-2d6b8e4a9c7f"}| Field | Description |
|---|---|
error | Always true on an error response |
message | Human readable reason. Validation messages are in Portuguese or English depending on the desk |
code | Stable machine readable code. See Error Codes |
request_id | Correlation identifier, also returned in the X-Request-ID response header. Quote it when contacting support |
Outside production the envelope also carries a details field with the raw internal error. In production the field is omitted.
Idempotency
Section titled “Idempotency”Every ingest endpoint is idempotent on a natural key chosen by the source system, scoped to the desk:
| Desk | Natural key |
|---|---|
| Fraud | external_id |
| MED | id, event_id, notification_id or med_id (first present), otherwise the SHA-256 of the raw body, stored and returned as sha256: followed by the hash |
| AML | alerta_externo_id |
| Onboarding | execution_id (onboarding envelope) or case_id (generic KYC payload) |
The first call for a key creates the case and answers 201. Any later call with the same key answers 200 with created: false (or is_new_case: false on the AML desk) and the identifier of the existing case. Nothing is overwritten: the case is immutable after creation and the analyst’s work is never replaced by a resend. This makes the endpoints safe to retry after a network timeout without generating duplicate work for the desk.
The idempotency window is unbounded: the key stays unique for the lifetime of the desk.
Fraud Alerts
Section titled “Fraud Alerts”POST /api/v1/ingest/fraudeScope: ingest:fraude. Opens a case on the Fraud desk from a transaction alert produced by a fraud detection engine (the Guardline FPP or a third party engine).
Request Fields
Section titled “Request Fields”The body is a JSON object. Only three fields are mandatory; everything else enriches the case. Alternative key names accepted for the same field are listed in the alias column. The complete body is preserved as the case payload, so keys that are not listed here are kept and visible to the analyst even though they are not indexed.
| Field | Type | Required | Aliases | Description |
|---|---|---|---|---|
external_id | string | yes | id, event_id | Unique identifier of the alert in the source system. Idempotency key |
subject_document | string | yes | documento | CPF or CNPJ of the investigated customer. Unlike the AML desk, the format is not validated: punctuation is not stripped and the digit count is not checked, so the value is stored exactly as sent and should be normalized by the caller |
transaction_amount | number | yes | amount, valor | Amount in BRL, must be greater than zero. Stored in cents |
subject_name | string | no | cliente_nome, nome | Customer name |
counterparty_name | string | no | contraparte_nome | Name of the counterparty (beneficiary) |
transaction_type | string | no | tipo_transacao | Transaction type, lowercased. Defaults to pix |
risk_level | string | no | risco | Only low, medium, high and critical are recognized. Derived from score when absent or empty; any other value leaves the case without a risk level |
score | integer | no | Engine score on a 0 to 1000 scale. See Score, Risk and Priority | |
priority | integer | no | 0 to 9, lower is more urgent. Defaults to 3. Values outside the range are clamped | |
sla_deadline | string (RFC 3339) | no | Explicit SLA deadline. When absent, the deadline comes from the SLA policy configured for the desk, and the case is created without a deadline when no policy applies. A value that cannot be parsed is silently ignored and the desk policy applies instead; no 400 is returned | |
fraud_analysis | object | no | Full decision explanation and customer context. See The fraud_analysis Block |
Score, Risk and Priority
Section titled “Score, Risk and Priority”The Fraud desk expects the engine score on the FPP scale (0 to 1000). The CMP stores score / 10, capped at 100, on the case so that queues and dashboards share one scale across desks. The division is an integer division, so the result is truncated and not rounded: 999 is stored as 99. The original value stays in the case payload.
When risk_level is absent or empty, it is derived from the raw score:
| Score (0 to 1000) | Derived risk_level |
|---|---|
| 950 or more | critical |
| 850 to 949 | high |
| 700 to 849 | medium |
| below 700 | low |
When neither risk_level nor score is sent, the case is created with risk_level = medium.
Derivation runs only when the field is absent or empty. Only the four values above are recognized, and an unrecognized non empty value skips the derivation and leaves the case with no risk level at all, so send one of the four values or omit the field entirely.
priority orders the case in the queue and feeds the distribution engine together with the score. It is independent from the risk level.
The fraud_analysis Block
Section titled “The fraud_analysis Block”fraud_analysis is what the analyst’s fraud analysis panel renders. It is stored verbatim and supplies the workflow explanation (stages, policies, rules and their contributions), the transaction detail with device and location, the customer profile with behavioral baselines and recent transactions, anomaly comparisons, and the customer and counterparty timelines. Send it on every alert so the panel reflects this case.
{ "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": [] }}All sub-blocks are optional. Send whatever the engine has: a sub-block that is not sent is simply not rendered, while a sub-block whose field types do not match the schema is discarded, so match the documented shape.
Examples
Section titled “Examples”Minimal alert:
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 }'Complete alert:
{ "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": { "...": "..." } }}Responses
Section titled “Responses”201 Created, new case:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "external_id": "fpp-case-002", "created": true}200 OK, the external_id already exists on the Fraud desk:
{ "case_id": "2ca0bd66-0ef9-44e6-9b47-6816264dd779", "external_id": "fpp-case-002", "created": false}The new case enters the Fraud desk with status new, business status aguardando_analise and a queued work item. The distribution engine assigns it to an available analyst of the entry queue.
Errors
Section titled “Errors”| Status | message | Cause |
|---|---|---|
| 400 | empty body | No body was sent |
| 400 | invalid json | The body is not valid JSON |
| 400 | external_id é obrigatório | Missing identifier |
| 400 | subject_document é obrigatório | Missing customer document |
| 400 | transaction_amount deve ser maior que zero | Missing, non numeric or non positive amount |
| 401 | missing api key, invalid api key | See API Tokens |
| 403 | token scope not authorized for this endpoint | Token scope is not ingest:fraude or ingest |
| 429 | ingest rate limit exceeded | Per token limit exceeded |
MED Requests
Section titled “MED Requests”POST /api/v1/ingest/medScope: ingest:med. Opens a case on the MED desk from a special refund mechanism request (Mecanismo Especial de Devolução) received by the institution.
Request Fields
Section titled “Request Fields”The MED desk was the first desk of the platform and accepts the widest set of key aliases so that existing PIX back office exports can be forwarded with minimal transformation. The complete body is preserved as the case payload.
| Field | Type | Required | Aliases | Description |
|---|---|---|---|---|
valor_solicitado | number | yes | amount, valor | Amount requested for refund, in BRL, greater than zero. Stored in cents |
id | string or number | recommended | event_id, notification_id, med_id | Identifier of the MED request in the source system. Idempotency key. When all are absent the SHA-256 of the raw body is used, stored and returned as sha256: followed by the hash, so a byte identical resend is still deduplicated. The body hash does not survive any change in serialization (key order, number formatting, a timestamp carried in the payload), so it does not replace sending a real identifier |
request_id | string | no | med_request_id, id | MED protocol number shown to the analyst. When absent it falls back to a value derived from a re-serialized copy of the payload, which is not guaranteed to match the external_id returned to the caller, so send it explicitly |
requested_at | string (RFC 3339) | no | created_at, event_at | When the request was opened. Defaults to the ingest time |
motivo | string | no | reason_code, reason, tipo | golpe (default), fraude or erro_operacional. Unknown values fall back to golpe |
status | string | no | med_status | aberta (default), em_analise, aguardando_instituicao, aprovado, negado or recusado. Unknown values fall back to aberta |
prazo_limite_at | string (RFC 3339) | no | deadline_at | Regulatory response deadline of the MED request |
recebedora | object | no | recipient, counterparty | Receiving institution: { "nome": "...", "ispb": "..." }. Under recebedora only nome is read; name works under recipient and counterparty. Flat recebedora_nome, counterparty_name and recebedora_ispb keys are accepted too. When no receiving institution name can be resolved the case is stored with a placeholder name |
recebedora_respondeu | boolean | no | Whether the receiving institution has already answered | |
valor_devolvido | number | no | Amount already refunded, in BRL | |
data_devolucao_at | string (RFC 3339) | no | When the refund happened | |
subject_name | string | no | cliente_nome, nome | Customer name |
subject_document | string | no | cliente_documento, documento | Customer CPF or CNPJ |
transaction_type | string | no | tipo_transacao | Defaults to pix |
risk_level | string | no | risk | low, medium, high or critical. Unknown values are ignored |
score | integer | no | 0 to 100. Unlike the Fraud desk, the MED score is not normalized: it must already be on the 0 to 100 scale, and values outside that range are rejected | |
priority | integer | no | 0 to 9, lower is more urgent. Defaults to 3 |
Example
Section titled “Example”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 }'Responses
Section titled “Responses”201 Created when the case is new, 200 OK with created: false when the identifier already exists on the MED desk:
{ "case_id": "8d1b3f4e-2a6c-4d7e-9b0a-1c2d3e4f5a6b", "external_id": "MED-2026-000123", "created": true}Errors
Section titled “Errors”| Status | message | Cause |
|---|---|---|
| 400 | empty body, invalid json | Malformed request |
| 400 | missing or invalid valor_solicitado | Amount absent, non numeric or not greater than zero |
| 401 | missing api key, invalid api key | See API Tokens |
| 403 | token scope not authorized for this endpoint | Token scope is not ingest:med or ingest |
| 429 | ingest rate limit exceeded | Per token limit exceeded |
AML Alerts
Section titled “AML Alerts”POST /api/v1/ingest/pldScope: ingest:pld. Opens or enriches a case on the AML desk (prevenção à lavagem de dinheiro) from a transactional alert produced by a monitoring engine. The AML desk differs from the others in two ways: alerts for a customer who already has an open case are consolidated into that case instead of opening a new one, and cash movements at or above the regulatory threshold are flagged as an automatic COE (comunicação de operação em espécie) that skips triage.
Request Fields
Section titled “Request Fields”| Field | Type | Required | Aliases | Description |
|---|---|---|---|---|
alerta_externo_id | string | yes | Unique identifier of the alert in the source system. Idempotency key. Limited to 100 characters; a longer value currently fails as a server error instead of a validation error, so keep within the limit | |
cpf_cnpj | string | yes | CPF (11 digits) or CNPJ (14 digits) of the investigated customer. Punctuation is stripped before validation | |
tipo_alerta | string | yes | Alert type code, case insensitive. See Alert Types | |
subject_name | string | recommended | cliente_nome, nome | Customer name |
score_risco | number | recommended | score | Risk score. Both 0 to 100 and 0 to 1000 scales are accepted; values above 100 are divided by 10 |
valor_total | number | recommended | valor | Total amount of the movement, in BRL. Used by the COE rule |
valor_total_centavos | integer | no | valor_centavos | Same amount in cents. Takes precedence over valor_total when both are present |
tipologia_sugerida | string[] | no | Typology codes suggested by the engine, for example ["PIX001", "EST001"] | |
dados_cliente | object | no | Free form customer context (declared income, activity, city, state). Stored verbatim and shown in the dossier | |
dados_transacionais | object | no | Free form transactional context (number of operations, period, channels). Stored verbatim | |
contrapartes | array | no | Counterparties involved. Stored verbatim | |
is_coe | boolean | no | Forces the automatic COE classification regardless of type and amount | |
priority | integer | no | 0 to 9, lower is more urgent. Defaults to 3. An automatic COE always lands at priority 1 | |
fpp_source_url | string | no | Deep link back to the alert in the source system, shown in the dossier |
Alert Types
Section titled “Alert Types”tipo_alerta must be one of the codes below. An unknown code is rejected with 404.
| Code | Description |
|---|---|
pix | PIX transfer |
ted | TED transfer |
doc | DOC transfer |
boleto | Boleto payment |
especie | Cash movement (notes). The only type eligible for the automatic COE rule |
cambio | Foreign exchange operation |
cartao | Card operation |
deposito | Deposit |
saque | Withdrawal |
transferencia | Generic transfer between accounts |
outro | Any other movement |
Processing Pipeline
Section titled “Processing Pipeline”Each alert goes through four steps in a single transaction:
- Idempotency. If an alert with the same
alerta_externo_idalready exists, the API answers 200 withis_new_case: falseandconsolidated_intoset to the case number that holds the original alert. No case or alert data is written; the resend is recorded in the audit trail. Two calls carrying the same identifier that arrive at the same instant are a different situation: one of them loses the race on the unique index and gets 409 with codeALREADY_EXISTS, which is safe to treat as a duplicate. - COE detection. The alert is an automatic COE when
is_coeistrue, or whentipo_alertaisespecieand the total amount is at or above R$ 50.000,00. An automatic COE always creates a new case (it is never consolidated), starts at AML statuspendente_geracao_xmlwith priority 1, and is projected asdecided/aprovadoon the generic case status so it shows up as ready for the COAF communication step. - Consolidation. When the alert is not a COE and the same
cpf_cnpjalready has an active AML case (any AML status other thanarquivado_triagem,arquivado,comunicado_coaf,retificadoorcancelado), the alert is attached to that case. The API answers 200 withis_new_case: falseandconsolidated_intoset to the existing case number. The analyst sees the new alert in the dossier of the case they are already working. A COE alert always opens its own case and is never consolidated into an existing active case. COE also requires the source system to mark the alert as a cash operation (tipo_alertaofespecie, or the explicitis_coeflag): the amount threshold alone does not trigger it. - New case. Otherwise a new AML case is created with status
new, business statusaguardando_analise, AML statusnovo, a 45 day selection deadline (prazo_selecao) and a human readable case number in the formPLD-YYYY-NNNNN. The API answers 201.
Examples
Section titled “Examples”Minimal alert:
{ "alerta_externo_id": "ALR-2026-000981", "cpf_cnpj": "12345678901", "tipo_alerta": "pix"}Recommended alert:
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" }'Cash movement that triggers an automatic COE:
{ "alerta_externo_id": "ALR-2026-000990", "cpf_cnpj": "12345678000199", "tipo_alerta": "especie", "subject_name": "Acme Serviços LTDA", "valor_total_centavos": 7500000}Responses
Section titled “Responses”201 Created, new case:
{ "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, duplicate alert or alert consolidated into an active case of the same customer:
{ "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"}| Field | Description |
|---|---|
case_id | UUID of the case that holds the alert |
case_pld_detail_id | UUID of the AML detail record of that case |
numero_caso | Human readable case number, the identifier operators quote |
is_new_case | true when a case was created by this call |
is_coe_automatica | true when the case is an automatic COE. Populated only when this call creates or consolidates the case: a plain idempotent replay returns false even for a COE case, so read it from the creation response and not from a replay |
consolidated_into | Present only when is_new_case is false: the case number that absorbed the alert, either because the alerta_externo_id was already known or because the customer had an active case |
alerta_externo_id | Echo of the request identifier |
To distinguish the two 200 situations compare numero_caso with what the source system expected: a duplicate returns the case of the original alert, a consolidation returns a case that was opened by a different alert.
Errors
Section titled “Errors”| Status | message | Cause |
|---|---|---|
| 400 | empty body, invalid json | Malformed request |
| 400 | alerta_externo_id is required | Missing identifier |
| 400 | cpf_cnpj must be a valid 11 or 14-digit document | After removing non digits the document is not 11 or 14 characters long |
| 400 | tipo_alerta is required | Missing alert type |
| 404 | pld_alert_type "xyz" not found | tipo_alerta is not one of the alert types; the message quotes the value that was sent |
| 401 | missing api key, invalid api key | See API Tokens |
| 403 | token scope not authorized for this endpoint | Token scope is not ingest:pld or ingest |
| 429 | ingest rate limit exceeded | Per token limit exceeded |
Onboarding Cases (KYC and KYB)
Section titled “Onboarding Cases (KYC and KYB)”POST /api/v1/ingest/kycScope: ingest:kyc. Opens a case on the onboarding desks: Cadastro PF (KYC, individuals) or Cadastro PJ (KYB, companies). There is no separate /ingest/kyb route; the desk is chosen by the flow_type of the payload.
The endpoint accepts two body shapes, discriminated by the event_type field:
| Shape | When | Discriminator |
|---|---|---|
| Onboarding envelope | The case comes from the Guardline ONP, or from an onboarding engine that follows the same contract. Supports KYC, KYC minor, legal representative and KYB flows, and carries terminal decisions back for reconciliation | event_type is cmp.case.review, cmp.case.approved or cmp.case.rejected |
| Generic KYC payload | A third party decision engine sends its full KYC analysis for an individual | event_type absent or any other value |
Both shapes answer with the same { case_id, external_id, created } body.
Request Signature (HMAC)
Section titled “Request Signature (HMAC)”Besides the API token, the endpoint can require an HMAC signature. The signature layer is enabled per instance, when a shared secret is provisioned during integration setup; until then only X-API-Key is enforced. When enabled, the signature is verified before the API token.
| Header | Value |
|---|---|
X-Guardline-Timestamp | Unix time in seconds (integer) at the moment of signing |
X-Guardline-Signature | Lowercase hexadecimal HMAC-SHA256 of {timestamp}.{body} |
Signing procedure:
- Serialize the body once. The exact bytes sent must be the bytes signed; do not re-serialize or pretty print after signing.
- Concatenate the timestamp, a literal dot and the raw body:
{timestamp}.{body}. - Compute HMAC-SHA256 of that string with the shared secret and encode the digest in 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"The timestamp must be within 5 minutes of the server clock (the tolerance is configurable per instance). Signature failures answer 401 with one of missing signature, missing timestamp, malformed timestamp, stale timestamp, malformed signature or invalid signature.
This is the same format the Guardline ONP uses for its outgoing webhooks, so an integrator that already verifies ONP webhooks can reuse the routine to sign calls to the CMP.
Onboarding Envelope
Section titled “Onboarding Envelope”The envelope is the body the Guardline ONP posts to the CMP when an onboarding execution is derived to manual analysis, and again when the execution reaches a terminal decision. The ONP API reference describes the producer side; this section describes what the CMP expects and does.
Event Semantics
Section titled “Event Semantics”event_type | Effect |
|---|---|
cmp.case.review | Creates the case and queues it on the desk. This is the only event that creates a case. A resend for the same execution_id answers 200 with created: false |
cmp.case.approved | Reconciles a terminal approval onto a case previously created by a review. If the case has no decision yet, the approval is recorded as the decision. Never creates a case |
cmp.case.rejected | Same as above for a rejection. The kyc_reject_* (or kyb_reject_*) decision code is chosen from rejection_reasons |
A terminal event for an execution_id that never produced a review answers 200 with an empty case_id and created: false: the execution was auto decided by the engine, never reached the desk, and the CMP deliberately does not fabricate a queued case for it. A terminal event never overwrites a decision an analyst already recorded.
Fields
Section titled “Fields”| Field | Type | Required | Description |
|---|---|---|---|
event_type | string | yes | See Event Semantics |
execution_id | string | yes | Identifier of the onboarding execution. Stored as the case external_id and used as the idempotency key |
flow_type | string | yes | kyc (adult individual), kyc_minor (minor with a legal representative), kyc_representative (the representative’s own execution, reviewed as its own case) or kyb (company). kyb routes to the Cadastro PJ desk, the others to Cadastro PF |
subject | object | yes | See below |
case_envelope | object | yes | See below |
event_id | string | no | Producer side event identifier, stored with the case |
fired_at | string (RFC 3339) | no | When the producer emitted the event |
tenant_id | string | no | Producer tenant, informational |
flow_id, flow_group_code | string | no | Flow and flow group of the execution, informational |
edd_flagged | boolean | no | The execution requires enhanced due diligence. Shown to the analyst |
rejection_reasons | string[] | no | Free form reasons behind a rejection. Used to classify the rejection decision code |
derivation_reasons | object[] | no | Why the engine derived the case to manual analysis: { "code", "label_ptbr", "severity", "source", "detail" }. Rendered verbatim in the dossier |
representative | object | no | kyc_minor only: { "execution_id", "status" } of the legal representative’s execution |
guardian_type | string | no | kyc_minor only: parent, mother, father or other |
representatives | object[] | no | kyb only: the company’s legal representatives, { "execution_id", "status", "name", "flag", "document", "document_type" }. flag is true when the representative was rejected or expired. Entries without both execution_id and name are dropped |
subject:
| Field | Type | Required | Description |
|---|---|---|---|
document | string | terminal events only | CPF or CNPJ, digits only (punctuation is stripped). A cmp.case.review may arrive with an empty document when the execution was derived before the identity step completed; the analyst resolves the identity on the desk |
document_type | string | no | cpf or cnpj |
name | string | no | Subject name, shown in the queue |
email, phone, birth_date | string | no | Captured in the case payload |
case_envelope:
| Field | Type | Required | Description |
|---|---|---|---|
priority | string | yes | P0, P1, P2 or P3, lower is more urgent. Mapped to the case priority 0 to 3. Any other value is rejected |
case_status | string | no | Engine status: approved, rejected, blocked, escalated or other. Drives the workflow decision shown to the analyst (see mapping below) |
decision_code | string | no | Engine decision: approved, rejected or pending_review (the short forms approve, reject and review are also accepted). Drives the terminal CMP decision on cmp.case.approved / cmp.case.rejected |
risk_level | string | no | low, medium, high or critical, case insensitive. Unknown values are ignored |
total_score | number | no | Engine score. Stored as the workflow score and, clamped to 0 to 100, as the case score used by queues |
sla_deadline_at, completed_at, expires_at | string (RFC 3339) | no | Producer timestamps. completed_at becomes the decision time on a terminal event |
Mapping to the Case
Section titled “Mapping to the Case”| Source | Case attribute | Rule |
|---|---|---|
case_envelope.case_status | Workflow decision shown on the desk | approved becomes ALLOW; rejected and blocked become BLOCK; anything else becomes REVIEW |
case_envelope.decision_code + rejection_reasons | Terminal decision code | approved records kyc_approve_onboarding (kyb_approve_onboarding for companies). rejected records kyc_reject_fraud_suspicion when the reasons mention fraud, suspicious or scam; kyc_reject_document_invalid when they mention document, invalid_doc, expired or illegible; kyc_reject_compliance otherwise. pending_review records nothing: the analyst decides |
case_envelope.priority | Case priority | P0 to P3 map to 0 to 3 |
case_envelope.risk_level | Case risk level | Lowercased; unknown values leave the risk level unset |
case_envelope.total_score | Case score | Rounded and clamped to 0 to 100 |
subject.name, subject.document | Queue columns | Shown in the desk listing |
Cases created by a cmp.case.review enter the desk with status new, business status aguardando_analise and a queued work item, exactly like the other desks. Only the envelope is persisted at ingest time; the full dossier (documents, biometrics, provider results, decision tree) is fetched from the ONP on demand when the analyst opens the case.
Examples
Section titled “Examples”KYC case derived to manual analysis:
{ "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"}KYB case derived to manual analysis:
{ "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}Terminal rejection reconciled onto the case above:
{ "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"]}Generic KYC Payload
Section titled “Generic KYC Payload”A third party onboarding or decision engine that does not implement the envelope can send its full KYC analysis of an individual. Three fields are mandatory; everything else is optional and, when present, is indexed so the desk can filter and display it. The complete body is preserved as the case payload. Keys are accepted in camelCase (as listed) and, for most of them, in snake_case.
Required Fields
Section titled “Required Fields”| Field | Type | Description |
|---|---|---|
case_id | string | Unique identifier of the analysis in the source system. Idempotency key. external_id and id are accepted as aliases |
subject.personal.cpf | string | CPF of the customer, 11 digits without punctuation. Also read from subject.basicData.cpf, subject.cpf.cpf or a top level subject_document |
decision.finalDecision | string | ALLOW, REVIEW or BLOCK, case insensitive. decision.final_decision is accepted |
Optional Blocks
Section titled “Optional Blocks”| Block | Fields read by the CMP |
|---|---|
subject.personal | name (also subject.basicData.name, subject.cpf.name, top level subject_name) |
decision | finalScore (0 to 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 | Contract version label, defaults to v1 |
Derived Attributes
Section titled “Derived Attributes”The CMP derives the case priority and risk level from the decision and the compliance, biometric and device signals:
| Attribute | Rule |
|---|---|
| Priority (lower is more urgent) | Base value by decision: BLOCK 1, REVIEW 3, ALLOW 5. Any sanctions hit forces 0. A PEP raises to at most 1. Lawsuits, government debts, facial.riskLevel = high, or a device flagged as VPN, emulator or rooted raise to at most 2 |
| Risk level | BLOCK is critical. REVIEW is high when the final score is at least 90% of the block threshold, otherwise medium. ALLOW is low. Sanctions or PEP raise low and medium to high |
| Score | decision.finalScore (or workflowExplanation.totalScore), clamped to 0 to 100 |
Example
Section titled “Example”{ "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 } }}This payload lands on the Cadastro PF desk with priority 1 (PEP) and risk level high (PEP raises medium to high).
Responses
Section titled “Responses”201 Created when the case is new, 200 OK with created: false on a resend or on a terminal reconciliation:
{ "case_id": "9e1c8b3a-1234-5678-9abc-def012345678", "external_id": "7b5c3d22-3e5f-4a7b-93b8-c6f5a2d4a4e2", "created": true}Errors
Section titled “Errors”| Status | message | Cause |
|---|---|---|
| 400 | empty body, invalid json | Malformed request |
| 400 | execution_id e obrigatorio | Envelope without execution_id |
| 400 | flow_type "x" invalido (esperado kyc, kyb, kyc_minor ou kyc_representative) | Unknown flow_type |
| 400 | subject e obrigatorio, case_envelope e obrigatorio | Missing envelope objects |
| 400 | subject.document e obrigatorio | Terminal event without a document |
| 400 | case_envelope.priority "x" invalida (esperado P0, P1, P2 ou P3) | Priority outside the accepted set |
| 400 | case_id e obrigatorio | Generic payload without identifier |
| 400 | subject.personal.cpf e obrigatorio | Generic payload without CPF |
| 400 | decision.finalDecision deve ser ALLOW, REVIEW ou BLOCK | Generic payload with an invalid decision |
| 401 | missing signature, invalid signature, stale timestamp and related | HMAC layer enabled and the signature failed |
| 401 | missing api key, invalid api key | See API Tokens |
| 403 | token scope not authorized for this endpoint | Token scope is not ingest:kyc or ingest |
| 429 | ingest rate limit exceeded | Per token limit exceeded |
Case Lifecycle
Section titled “Case Lifecycle”Desks and Queues
Section titled “Desks and Queues”Cases live on desks (operations). Each desk has an entry queue where new cases land and a supervisor queue for escalations. The desk code is the stable identifier used by the API, for example in the cross-desk transfer request; queue display names are chosen at deployment time and can be renamed by an administrator.
| Desk code | Desk | Case type | Ingest endpoint |
|---|---|---|---|
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 | AML | pld | /api/v1/ingest/pld |
comercial | Comercial | keeps the type of the originating desk | none, receives cases by transfer |
Statuses
Section titled “Statuses”Every case carries a generic status, shared by all desks, and a business status that refines it:
| Generic status | Meaning |
|---|---|
new | Created by ingest, waiting for distribution or pull |
in_analysis | Assigned to an analyst |
escalated | Sent to the supervisor queue |
decided | A decision was recorded |
closed | Closed |
failed | The occurrence could not be opened or processed; the case is released for reprocessing |
The AML desk adds its own workflow on top (novo, em_triagem, em_investigacao, aguardando_revisao_investigacao, aguardando_comite, pendente_geracao_xml, xml_gerado, comunicado_coaf, erro_siscoaf, retificado, cancelado, arquivado, among others). Its transitions are the ones published to receivers as outbound webhooks.
SLA deadlines are computed at ingest from the SLA policy of the desk and status (configurable per desk, queue and status, with a warning threshold before expiry). The Fraud desk also accepts an explicit sla_deadline in the payload.
Cross-desk Transfers
Section titled “Cross-desk Transfers”An analyst can hand a case to another desk when the analysis reveals a different nature (fraud suspicion on an onboarding case, money laundering indicators, need for a commercial opinion). The case keeps its type and its history, and returns the same way. Pairs enabled today:
| From | To |
|---|---|
Cadastro PF (kyc) | Fraude, AML, Comercial (and back) |
Cadastro PJ (kyb) | Fraude, AML, Comercial (and back) |
Transfers are recorded in the audit trail with origin, destination, actor and justification. They are not visible to the source system through the API; the decision that eventually closes the case is.
Decision Feedback
Section titled “Decision Feedback”How the source system learns about the analyst’s decision depends on the desk.
Onboarding Cases
Section titled “Onboarding Cases”For cases opened through the onboarding envelope, the CMP pushes the analyst’s decision (approve, reject) back to the Guardline ONP as soon as it is recorded, referencing the execution_id. Only these terminal decisions propagate: intermediate decisions such as an escalation or a request for additional information stay in the CMP and produce no ONP event. The ONP then resumes the execution and emits its own onboarding events to the integrator. An integrator that uses ONP and CMP together therefore consumes a single webhook stream, the one described in the ONP API reference, and does not need to poll the CMP.
The pushback is delivered through the same durable outbox as the outbound webhooks below. During a temporary ONP outage it is retried a small number of times over roughly two minutes and is then marked exhausted; there is no automatic redrive after that, so recovering the notification requires a manual requeue by Guardline operations.
AML Cases: Outbound Webhooks
Section titled “AML Cases: Outbound Webhooks”The AML desk publishes its workflow milestones to HTTP receivers configured for the instance. A receiver is defined by a name, a target URL, an HMAC secret and the list of events it subscribes to, and is provisioned by Guardline during integration setup. Several receivers can subscribe to the same event.
Events
Section titled “Events”| Event | Emitted when | Resulting pld_status |
|---|---|---|
triagem_concluida | The alert was archived during triage, after investigation, or by the director when archiving a case the committee rejected. In that third case the delivery carries action: "pld_arquivar_reprovado" and arrives right after comite_reprovado, so route on payload.action and not on the event name alone | arquivado_triagem or arquivado |
comite_aprovado | The committee session closed with approval of the communication | aprovado_comite |
comite_reprovado | The committee session closed rejecting the communication | reprovado_comite |
coaf_registrado | The COAF communication was registered (SISCOAF protocol received) | comunicado_coaf |
coaf_erro | The SISCOAF submission failed and the error was recorded | erro_siscoaf |
coaf_retificado | The communication was rectified | retificado |
coaf_cancelado | The communication was cancelled | cancelado |
Protocol numbers, hashes and receipts of the COAF communication are deliberately not carried in the webhook. They are available to authorized users on the desk.
Payload
Section titled “Payload”Every delivery posts the same envelope; the payload block is stable across the AML events so a single decoder handles all of them.
{ "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" }}| Field | Description |
|---|---|
event | Event name from the table above |
delivery_id | Stable identifier of this delivery, identical across retries. Use it for deduplication |
correlation_key | The AML case number |
case_id | UUID of the case |
attempt | 1 for the first delivery, 2 and 3 for the retries |
timestamp | When this attempt was sent |
payload.action | The workflow action that produced the transition (for example pld_arquivar_triagem, pld_registrar_coaf, pld_retificar, comite_aprovado) |
payload.pld_status | The resulting AML status |
payload.motivo | Present only on coaf_retificado and coaf_cancelado: the justification entered by the operator |
payload.occurred_at | When the transition was committed |
Headers
Section titled “Headers”| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Webhook-Event | The event name, for routing without parsing the body |
X-Webhook-Delivery | The delivery identifier, same value as delivery_id in the body |
X-Webhook-Signature | HMAC-SHA256 of the raw body, hexadecimal |
Signature Verification
Section titled “Signature Verification”The signature covers the raw request body only (there is no timestamp component, unlike the inbound onboarding signature).
- Read the raw body bytes, before any JSON parsing.
- Compute HMAC-SHA256 of the body with the receiver’s secret and encode it in hexadecimal.
- Compare with
X-Webhook-Signatureusing a constant time comparison.
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)Retry Policy
Section titled “Retry Policy”A delivery is successful when the receiver answers any 2xx status within 10 seconds. Otherwise the CMP retries:
| Attempt | Delay after the previous failure |
|---|---|
| 2 | On the next drain after the failure |
| 3 | At least 30 seconds after that |
The outbox is drained by a job that runs every 60 seconds, so the drain interval dominates the real timing and the whole retry window is about two minutes. After the third failure the delivery is marked exhausted and is no longer retried automatically. Deliveries are persisted in an outbox before the first attempt, so a CMP restart never loses an event; the guarantee holds once the delivery row is written. Requeuing an exhausted delivery is a manual operation performed by Guardline operations on request, not a self-service API.
Endpoint Configuration
Section titled “Endpoint Configuration”| Requirement | Detail |
|---|---|
| Protocol | HTTPS required |
| TLS version | 1.2 or higher |
| Success response | Any 2xx code, body ignored |
| Timeout | Respond within 10 seconds |
| Idempotency | Deduplicate on X-Webhook-Delivery (or delivery_id) |
Receive the webhook, persist or enqueue it, and answer 200 immediately. Do the processing asynchronously.
Fraud and MED Cases
Section titled “Fraud and MED Cases”Decisions on the Fraud and MED desks are recorded in the CMP with their justification, decision code and operational effects (cautionary hold, preventive account block, list additions) and are visible in the case detail and the audit trail of the console. The outbound webhook pipeline is shared by all desks, but no event is published for those desks at the moment; when the institution needs the fraud or MED outcome pushed to its core system, the subscription is enabled as part of the integration setup.
Token Administration
Section titled “Token Administration”Tokens are managed by CMP administrators. The endpoints below require the console session cookie of a user with the admin role, which the AML director role inherits, so a AML director can manage tokens as well; they are not callable with an API token. The same operations are available in the console.
Listing Tokens
Section titled “Listing Tokens”GET /api/v1/api-tokensReturns every token with its label, prefix, scope, activity flag, rate limit, expiry, usage counter and last use. Token values are never returned.
Creating a Token
Section titled “Creating a 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}| Field | Required | Description |
|---|---|---|
label | yes | Display name |
scope | no | One of the scopes. Defaults to ingest |
rate_limit_max | no | Requests per window. Defaults to 60 |
rate_limit_window_seconds | no | Window length. Defaults to 60 |
expires_in_days | no | Days until expiry. 0 or absent means no expiry |
Response 201:
{ "ok": true, "id": "c9a1e845-a565-43b6-ac29-2d3072b027b9", "token": "cmp_e188905ec47a80ec035fe31a3264bf1a5f9231f1353ea24717cf311740305f11", "token_prefix": "cmp_e188905e", "label": "fpp-producao", "scope": "ingest:fraude"}The token field appears only in this response. Store it in the secret vault of the source system immediately; it cannot be recovered later. If it is lost, create a new token and rotate.
Rotating, Deactivating and Deleting
Section titled “Rotating, Deactivating and Deleting”Rotation without downtime: create the new token, configure it in the source system, confirm a successful ingest, then deactivate the old one.
PATCH /api/v1/api-tokens/{id}/toggle{ "is_active": false }Deactivation keeps the token record and its usage history for audit. A deactivated token answers 401 invalid api key and can be reactivated with is_active: true.
DELETE /api/v1/api-tokens/{id}Deletion is permanent.
Inbound Webhook Catalog
Section titled “Inbound Webhook Catalog”GET /api/v1/settings/webhooksReturns the catalog of every inbound endpoint the instance exposes (the four ingest routes plus the communication provider callbacks for WhatsApp, SMS and voice), with the path, accepted methods, authentication type and header, and the required token scope. The catalog is derived from the route table of the running version, so it is the quickest way for an administrator to confirm the exact URL to hand to a source system.
Complete Flow Diagrams
Section titled “Complete Flow Diagrams”Fraud Alert From an External Engine
Section titled “Fraud Alert From an External Engine”Onboarding Case Derived by the ONP
Section titled “Onboarding Case Derived by the ONP”AML Alert With COAF Communication
Section titled “AML Alert With COAF Communication”Error Reference
Section titled “Error Reference”HTTP Status Codes
Section titled “HTTP Status Codes”| Status | Meaning |
|---|---|
| 200 | Request processed; the case already existed (idempotent replay, consolidation or reconciliation) |
| 201 | Case created |
| 400 | Malformed body or failed validation. Fix the request before retrying |
| 401 | Missing or invalid API token, or failed HMAC signature |
| 403 | Token scope does not cover the endpoint |
| 404 | A referenced code does not exist (for example an unknown AML tipo_alerta) |
| 409 | Conflict with the current state of the case |
| 429 | Rate limit exceeded. Retry after the window |
| 500 | Internal error. Retry with backoff and quote the request_id to support |
| 503 | A dependency is unavailable. Retry with backoff |
Error Codes
Section titled “Error Codes”code | Typical status | Description |
|---|---|---|
BAD_REQUEST | 400 | A field failed validation; message says which |
VALIDATION_ERROR | 400 | The body is empty or is not valid JSON |
UNAUTHORIZED | 401 | Credential problem |
FORBIDDEN | 403 | Scope problem |
NOT_FOUND | 404 | Unknown reference code |
CONFLICT | 409 | State conflict |
DUPLICATE_EXTERNAL_ID | 409 | The ingest identifier was already used on that desk |
ALREADY_EXISTS | 409 | The AML desk already holds an alert with that alerta_externo_id |
RATE_LIMITED | 429 | Rate limit exceeded |
SERVICE_UNAVAILABLE | 503 | Dependency unavailable |
INTERNAL_ERROR | 500 | Unexpected failure |
Recommended handling: treat 400, 401, 403 and 404 as permanent for that request (alert the operator, do not retry blindly); treat 429, 500 and 503 as transient (retry with exponential backoff, the idempotency keys make retries safe).
Endpoint Summary
Section titled “Endpoint Summary”| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/v1/ingest/fraude | API token (ingest:fraude) | Open a case on the Fraud desk |
| POST | /api/v1/ingest/med | API token (ingest:med) | Open a case on the MED desk |
| POST | /api/v1/ingest/pld | API token (ingest:pld) | Open or consolidate a case on the AML desk |
| POST | /api/v1/ingest/kyc | API token (ingest:kyc) + optional HMAC | Open or reconcile a case on the Cadastro PF or Cadastro PJ desk |
| GET | /api/v1/api-tokens | Admin session | List API tokens |
| POST | /api/v1/api-tokens | Admin session | Create an API token |
| PATCH | /api/v1/api-tokens/{id}/toggle | Admin session | Activate or deactivate a token |
| DELETE | /api/v1/api-tokens/{id} | Admin session | Delete a token |
| GET | /api/v1/settings/webhooks | Admin session | Catalog of inbound endpoints |
| GET | /healthz, /readyz, /livez | None | Liveness and readiness probes |
Version 18, updated on