Erros e códigos
Onde cada formato está declarado
Seção intitulada “Onde cada formato está declarado”O contrato OpenAPI declara, em cada operação, os status de erro que ela devolve e, em cada status, as formas possíveis do corpo, com schema nomeado. Quando um status tem mais de uma forma, o contrato usa anyOf: o corpo casa com pelo menos uma delas. As formas se distinguem pelo tipo de error (texto ou objeto) e pela presença de code na raiz.
| Schema no contrato | error |
Código | Quem responde assim |
|---|---|---|---|
ErrorResponseCoded |
objeto | error.code |
Chave de API, IP, ambiente e escopo da chave, limite da chave, rota não encontrada e parte dos erros de cadastro de clientes e carteiras |
KYTPublicErrorResponse |
objeto | error.code |
Análise de transação, consulta da análise, decisão do cliente e laudo KYT |
VASPWalletErrorResponse |
objeto | error.code |
Conflitos de carteira (409), com ids extras em error |
ErroTexto |
texto | nenhum | Maior parte dos handlers de cadastro, webhooks, ledger e leituras regulatórias; falha inesperada (500); escudo de borda e limitadores indisponíveis (503); recusa por padrão de ataque (403) |
ErroTextoComCodigo |
texto | code na raiz |
Corpo acima de 1 MiB (413) e leitura do módulo contratado indisponível (503) |
TenantAccessRefusalResponse |
texto | code na raiz |
Conta sem direito comercial (402), conta que não pode usar a API (403), estado da conta indisponível (503) |
ModuleNotEnabledResponse |
texto | code na raiz |
Tenant sem o módulo de KYT contratado (403) |
ErroLimite |
texto | code às vezes |
Limitadores anteriores à operação (429), com retry_after quando há espera definida |
Objeto com código
Seção intitulada “Objeto com código”É o formato da camada de chave de API e de parte da superfície /vasp/*.
{ "success": false, "error": { "code": "insufficient_scope", "message": "API key missing required scope: vasp:read" }}Envelope das operações de KYT
Seção intitulada “Envelope das operações de KYT”POST /vasp/screenings, GET /vasp/screenings/{id}, a decisão do cliente (PATCH /vasp/screenings/{id}/client-decision) e o laudo (POST /vasp/kyt-report) usam um objeto mais completo, que diz se vale repetir e em quanto tempo:
{ "success": false, "error": { "code": "KYT_PERSISTENCE_UNAVAILABLE", "http_status": 503, "message": "O screening não pôde ser consultado.", "action": "Repita a requisição mais tarde.", "retriable": true, "retry_after_seconds": 30, "request_id": "…" }}retry_after_seconds e request_id só aparecem quando têm valor. Mesmo nessas operações, as camadas anteriores (chave, conta, limites, módulo) respondem nos próprios formatos.
Texto simples
Seção intitulada “Texto simples”{ "success": false, "error": "Carteira não encontrada."}Texto com código na raiz
Seção intitulada “Texto com código na raiz”O código fica ao lado de error, e não dentro dele. A recusa da conta e a do módulo trazem details.
{ "success": false, "code": "TENANT_SUSPENDED", "error": "Sua conta está suspensa. Entre em contato com o suporte da Sentinex Risk."}Limitadores
Seção intitulada “Limitadores”{ "success": false, "error": "Too many requests. Please try again later.", "retry_after": 3600}O escudo de borda acrescenta code IP_BANNED, com reference para citar ao suporte, e os limites da busca de clientes e do laudo acrescentam detail. Os valores e a ordem dos limites estão em Limites de requisição.
Um consumidor defensivo faz assim:
function extrairCodigo(body) { if (!body) return null; if (typeof body.code === 'string') return body.code; // código na raiz if (body.error && typeof body.error === 'object') { // objeto com código return body.error.code ?? null; } return null; // texto simples}Onde fica o request_id
Seção intitulada “Onde fica o request_id”O identificador de correlação volta no header X-Request-ID da resposta, ecoando o que você enviou ou gerado pelo servidor. Só o envelope das operações de KYT o repete no corpo, em error.request_id. Nos demais formatos ele não vem no JSON: guarde o header.
Mapeamento HTTP da API de KYT
Seção intitulada “Mapeamento HTTP da API de KYT”O status é a parte estável do contrato. Ramifique por ele.
4xx: erro do cliente
Seção intitulada “4xx: erro do cliente”| HTTP | Quando acontece | Ação recomendada |
|---|---|---|
| 400 | Corpo inválido, campo ausente, UUID malformado, enum fora do domínio | Corrija o payload, não retente |
| 401 | Sem chave de API (missing_api_key) ou chave desconhecida, revogada ou expirada (invalid_api_key) |
Corrija a credencial |
| 402 | Conta sem direito comercial (TRIAL_EXPIRED) |
Fale com a Sentinex Risk. A requisição não foi processada |
| 403 | Chave recusada pelo IP, ambiente ou escopo; conta suspensa, não operacional ou com a API desligada; módulo de KYT não contratado; padrão de ataque na requisição | Ajuste escopo, ambiente, IP ou contratação. Não retente |
| 404 | Recurso inexistente ou pertencente a outro tenant; com chave válida, caminho ou método que não corresponde a operação servida (route_not_found) |
Não retente |
| 409 | Conflito de estado, recurso duplicado, Idempotency-Key reutilizada com corpo diferente |
Releia o estado antes de decidir |
| 413 | Corpo acima de 1 MiB (BODY_TOO_LARGE) |
Reduza o corpo |
| 422 | Regra de negócio recusou a operação | Leia a mensagem, corrija a intenção |
| 429 | Limite de chave, de IP, de hora ou da operação atingido | Respeite Retry-After quando vier (ver Limites de requisição) |
5xx: erro do servidor
Seção intitulada “5xx: erro do servidor”| HTTP | Quando acontece | Ação recomendada |
|---|---|---|
| 500 | Falha inesperada. A mensagem pública é genérica de propósito, para não vazar interno | Retry com backoff. Persistindo, abra ticket com o X-Request-ID |
| 502 | Provedor externo respondeu erro ao montar a evidência do laudo | Retry com backoff, sem presumir fallback |
| 503 | Dependência indisponível: verificação da chave, armazenamento de contadores, estado da conta, leitura do módulo contratado, transação da requisição ou serviço da operação. Não presuma conclusão; reconcilie com a mesma chave de idempotência | Retry com backoff; quando vier Retry-After: 1, a transação conflitou e pode ser repetida em seguida. Em decisão de risco, a plataforma prefere segurar a operação a aprovar sem dado |
Códigos da API de KYT, por camada
Seção intitulada “Códigos da API de KYT, por camada”| Camada | Status | Códigos |
|---|---|---|
| Chave de API | 401, 403, 503 | missing_api_key, invalid_api_key, api_key_ip_not_allowed, api_key_environment_required, api_key_environment_not_allowed, insufficient_scope, api_key_verification_unavailable (503: não foi possível verificar a chave; repita com backoff, não troque a credencial) |
| Limite da chave | 429, 503 | api_key_rate_limit_exceeded, rate_limit_unavailable, api_key_rate_limit_invalid |
| Conta | 402, 403, 503 | TRIAL_EXPIRED, TENANT_SUSPENDED, TENANT_NOT_OPERATIONAL, API_ACCESS_DISABLED, TENANT_ACCESS_DENIED, TENANT_ACCESS_STATE_UNAVAILABLE |
| Módulo contratado | 403, 503 | MODULE_NOT_ENABLED, MODULE_STORE_UNAVAILABLE |
| Escudo de borda | 403, 413, 429 | travessia de diretório na URL (403, sem código), BODY_TOO_LARGE, IP_BANNED |
| Rota | 404 | route_not_found |
| Análise, decisão e laudo | 400 a 503 | KYT_*, por exemplo KYT_REQUEST_INVALID, KYT_SCREENING_NOT_FOUND, KYT_IDEMPOTENCY_CONFLICT, KYT_INPUT_UNSCREENABLE, KYT_DEPENDENCY_UNAVAILABLE |
| Clientes e carteiras | 400, 409, 422 | invalid_customer, invalid_client_id, invalid_idempotency_key, invalid_customer_id, invalid_cursor, invalid_cpf_hash, invalid_cnpj_hash, invalid_risk_classification, invalid_kyc_tier, personal_filter_requires_post, invalid_search_body, invalid_wallet, invalid_wallet_id, invalid_mode, customer_already_exists, client_not_found, address_mismatch, wallet_already_exists, wallet_is_institutional, wallet_duplicate_legacy, wallet_has_open_process, wallet_address_active_elsewhere, KYT_IDEMPOTENCY_CONFLICT |
| Leituras regulatórias | 503 | regulatory_unavailable |
A lista pode crescer: trate código desconhecido como erro genérico do status.
Estratégia de retry
Seção intitulada “Estratégia de retry”const RETRYABLE = new Set([408, 429, 500, 502, 503]);
async function callWithRetry(fn, maxAttempts = 4) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (err) { const inProgress = err.status === 409 && err.body?.error?.code === 'KYT_IDEMPOTENCY_IN_PROGRESS'; if ((!RETRYABLE.has(err.status) && !inProgress) || attempt === maxAttempts) throw err; const retryAfter = err.headers?.['retry-after']; const delayMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : (err.body?.error?.retry_after_seconds ?? Math.min(2 ** attempt, 30)) * 1000; await new Promise(r => setTimeout(r, delayMs + Math.random() * 500)); } }}Como reportar um erro
Seção intitulada “Como reportar um erro”Quando abrir ticket no suporte (dev-support@sentinexrisk.com), envie:
- O
X-Request-IDda resposta de erro - Timestamp da chamada (UTC)
- Endpoint mais método HTTP
tenant_id(não envie payload com PII)
Prazo de suporte é o contratado, não um prazo inferido do código. Alertas de screening têm prazo operacional de 1 hora (CRITICAL) ou 4 horas (HIGH); isso não comprova resposta do suporte nem resolução automática.
HOLD não é sinônimo de erro HTTP
Seção intitulada “HOLD não é sinônimo de erro HTTP”HTTP 200 com data.fail_closed: true e recommendation: HOLD registra uma análise com evidência incompleta. Retenha e crie nova análise após recuperar a fonte, com nova chave. 503 KYT_IDENTITY_UNAVAILABLE também traz data.recommendation: HOLD, mas não cria análise nem cobra. Em ambos os casos, não libere por ausência de dado. Veja Como as regras decidem.