Pular para o conteúdo

Versionamento e changelog

O contrato do cliente KYT é o arquivo openapi.yaml publicado neste site, e a versão dele é o campo info.version. Ele descreve as operações que a API de produção serve a uma integração autenticada por chave de API, todas relativas a https://crypto-api.sentinexrisk.com/api/v1.

A versão do contrato do cliente é própria. A versão do serviço que GET /api/v1/version devolve identifica o binário em execução, e a especificação interna da plataforma e os SDKs têm numeração deles: nenhuma das duas indica mudança deste contrato.

Regra O que significa para você
O contrato v1 do primeiro cliente é a linha 8.x Toda versão 8.x.y roda sob /api/v1. A partir do congelamento, toda versão nova é compatível com a anterior
A linha congela no primeiro integrador info.x-snx-congelamento diz a fase. Com pre-integracao, ainda não há integrador em produção, e uma operação que não serve ao produto KYT pode sair do contrato, sempre nomeada em código na entrada do changelog e marcada como mudança incompatível. Só remoção de operação é permitida assim; qualquer outra mudança incompatível segue proibida em qualquer fase. Quando o primeiro integrador recebe a chave, o valor passa a congelado desde dd/mm/aaaa, e a fase não volta
Mudança incompatível só com /api/v2 Depois do congelamento, remover operação ou campo, tornar obrigatório o que era opcional, mudar tipo ou restringir valores aceitos não acontece sob /api/v1. Uma mudança assim exige um prefixo novo, /api/v2, com a major seguinte do contrato. Hoje não existe /api/v2
Mudança aditiva sobe o minor Operação nova, campo novo na resposta ou parâmetro opcional novo: 8.0.0 vira 8.1.0
Correção sobe o patch Texto de descrição, exemplo ou erro de digitação no contrato: 8.0.0 vira 8.0.1
Toda mudança do contrato tem entrada neste changelog A entrada traz a versão, a data e o que mudou

A versão só sobe quando o conteúdo deste contrato muda: mudança na plataforma que não altera o contrato do cliente não gera versão nem entrada aqui. Para ler versões futuras sem quebrar, trate campo desconhecido na resposta e valor desconhecido de enum como possíveis, sem falhar.

Uma verificação automática no processo de publicação do contrato reprova:

  • mudança no conteúdo do contrato do cliente sem aumento de info.version, ou versão que desce;
  • versão do contrato sem entrada neste changelog;
  • mudança de major sem mudança do prefixo /api/v1;
  • mudança incompatível sob o mesmo prefixo, mesmo quando a versão sobe só o minor ou o patch, depois do congelamento, inclusive a potencialmente incompatível, como remover propriedade opcional da resposta ou restringir tamanho e formato aceitos na requisição;
  • antes do congelamento, operação removida que não esteja nomeada em código (MÉTODO /caminho) na entrada do changelog;
  • contrato sem info.x-snx-congelamento, com data de congelamento no futuro, ou que volte de congelado para pre-integracao.

O contrato publicado neste site é gerado a partir da mesma fonte que a API usa, e as páginas da referência da API são geradas desse arquivo. Uma página que diverge do contrato reprova a publicação.

Correção de texto voltado ao integrador. Nas descrições do contrato, a chave de API identifica a empresa e carrega os escopos; nenhum header extra de empresa é necessário; a chave é criada no painel por um administrador da empresa. Não há mudança de operação, campo de payload ou comportamento servido.

Mudança aditiva, sem operação nova e sem campo removido. POST /vasp/screenings e GET /vasp/screenings/{id} ganham o campo opcional not_covered, no topo da resposta, com as dimensões que esta versão não analisa por desenho: exposição on-chain (exposures, graph e typologies, causa CAPABILITY_NOT_OFFERED) e blocklist de rede sem base de golpes carregada (causa NETWORK_NOT_COVERED; a base atual cobre ETH e as redes EVM que a compartilham: BASE, ARB, BSC, MATIC, AVAX, OP e FTM); se a rede tiver endereços gravados por importação anterior, a consulta é feita e o resultado é medido. Cada item traz rule_id, source e cause. ALLOW com entradas em not_covered significa que essas dimensões não foram analisadas, e não que o resultado delas foi negativo. Falha real do provedor, timeout ou erro de consulta continua retendo a triagem em HOLD e não entra neste campo. Triagens gravadas antes desta versão não trazem o campo; trate a ausência como lista vazia.

Correção de documentação do sandbox. A resposta 429 das chamadas VASP autenticadas passa a declarar os limites já aplicados pelo simulador, 60 requisições por minuto por key, 300 por minuto por tenant e 10.000 por dia UTC por tenant, além dos headers Retry-After, X-RateLimit-Limit e X-RateLimit-Remaining. Não há mudança de operação, campo de payload ou comportamento servido.

Contrato em pré-integração (info.x-snx-congelamento: pre-integracao). Duas operações entram no contrato; nenhuma sai. As demais mudanças são aditivas, esclarecem recusas que já existiam ou ajustam o formato de dados não medidos, como descrito abaixo.

  • POST /vasp/customers/batch: matrícula de clientes finais em lote, de 1 a 1.000 por chamada, com Idempotency-Key obrigatória e resposta 207 com o resultado de cada item. Os limites próprios do lote, por chave e por tenant, respondem 429 com Retry-After: 3600 e o corpo ErroLimiteDoLote; a recusa de acesso (403) tem as mesmas formas das demais operações.
  • PATCH /vasp/screenings/{id}/tx-hash: depois de pré-checar um saque e transmitir a transação, o cliente anexa o hash ao mesmo screening. O registro é imutável e idempotente: repetir o mesmo hash devolve 200 com idempotent_replay, outro hash devolve 409 KYT_TX_HASH_ALREADY_ATTACHED e hash fora do formato da rede devolve 400 KYT_TX_HASH_INVALID. Não reavalia o screening, não muda a recomendação nem a decisão do cliente e não gera cobrança. A existência da transação na rede não é medida (on_chain_verification NOT_MEASURED).
  • GET /vasp/screenings/{id} passa a trazer data.transaction (hash, instante e origem) depois de um anexo pelo PATCH. O campo é opcional e não aparece enquanto não houver anexo.
  • POST /vasp/screenings e GET /vasp/screenings/{id}: a resposta ganha o campo opcional label_evidence, com as etiquetas de bloqueio de emissor de stablecoin (USDT e USDC) lidas de eventos públicos do contrato do emissor. O campo só aparece quando há evento coletado para o endereço consultado, e a ausência de evento não prova que o endereço esteja livre. O efeito é sempre LABEL_ONLY: bloqueio de emissor é fato sobre o endereço, não é sanção e não altera o score. capabilities.labels passa a valer MEASURED também quando há essa etiqueta.
  • POST /vasp/screenings: operation_at mais de 5 minutos adiante do relógio do servidor responde 400 KYT_OPERATION_AT_IN_FUTURE, antes de qualquer análise, persistência ou cobrança. Até 5 minutos é aceito como folga de relógio, e a ausência do campo segue como antes. A tolerância é parâmetro operacional, não prazo de norma.
  • POST /vasp/screenings e GET /vasp/screenings/{id}: dado não medido deixa de aparecer como zero ou como resultado negativo. amount_brl sai null quando a quantidade ou a cotação não foi medida (antes saía 0), e sanctions.hit sai null com sanctions.state NOT_MEASURED quando a sanção não foi medida (antes saía false); null nunca vale como limpo. É mudança de formato da resposta, feita antes do primeiro integrador (a linha segue em pre-integracao), e quem integrar já deve tratar null nesses dois campos.
  • POST /vasp/screenings e GET /vasp/screenings/{id}: obligations.travel_rule passa a declarar os valores UNKNOWN, REQUIRED e NOT_REQUIRED. O servidor já emitia REQUIRED e NOT_REQUIRED; UNKNOWN é novo e sai quando a Travel Rule não é informada na requisição. Trate qualquer valor novo nesse campo sem falhar.

Correção da base canônica de produção do contrato: as chamadas da integração KYT devem usar https://crypto-api.sentinexrisk.com/api/v1. A base anterior, https://api.crypto.sentinexrisk.com/api/v1, não é mais a referência de produção. O sandbox permanece em https://sandbox.api.crypto.sentinexrisk.com/api/v1.

Contrato em pré-integração (info.x-snx-congelamento: pre-integracao). Nenhuma operação entra nem sai do contrato. Esta versão traz o desfecho HOLD da análise que conclui sem uma evidência necessária, mudanças aditivas no laudo KYT, o formato das chaves emitidas a partir da 2.16.0 e o sandbox do desenvolvedor.

  • POST /vasp/screenings: quando uma evidência necessária fica indisponível ou incompleta (contraparte não triável, provedor indisponível, timeout ou resposta incompleta), a análise conclui com HTTP 200, recommendation HOLD, fail_closed verdadeiro e o motivo em reasons, com a orientação de reprocessar com a mesma Idempotency-Key. Antes, esse desfecho respondia 503 KYT_DEPENDENCY_UNAVAILABLE.
  • HOLD é retenção temporária, não bloqueio e não confirmação de risco. Ele nunca vira ALLOW, nem pela política do tenant. DENY continua restrito a sanção ou regra de bloqueio configurada pelo tenant e prevalece sobre HOLD.
  • A recomendação é da Sentinex; a decisão operacional e o desfecho de suspeição continuam sendo do cliente, registrados em client_decision.
  • O 503 permanece para ausência de resultado do motor, política do tenant ilegível e falha de persistência, auditoria, medição ou infraestrutura, quando nada é registrado nem cobrado.
  • GET /vasp/screenings/{id}, o replay idempotente, o webhook e a decisão do cliente devolvem HOLD para a mesma análise.

Laudo KYT (POST /vasp/kyt-report, formatos pdf, html e json):

  • report.id passa a ser o identificador único da emissão, um ULID cujo instante é o da emissão. É o mesmo valor do nome do arquivo e da trilha de auditoria. Até a 2.15.x ele repetia o case_id, e dois laudos da mesma carteira saíam com o mesmo report.id.
  • case.case_id fica separado e estável. Passa a ter a forma KYT-WALLET-<REDE>-<hash do endereço canônico>, com a rede pelo nome canônico em maiúsculas (ETHEREUM, e não o apelido digitado) e o hash sobre o endereço canônico. Grafias do mesmo endereço (checksum ou minúsculo) e da mesma rede dão o mesmo case_id.
  • Compatibilidade do histórico: laudos emitidos antes da 2.16.0 trazem o case_id na forma antiga, calculada sobre a rede e o endereço como foram digitados, e report.id igual a esse case_id. A mesma carteira pode aparecer em dois grupos, um antigo e um novo, até o histórico anterior expirar. Não una os dois grupos pelo texto do case_id.
  • O par endereço e rede é validado antes de qualquer consulta externa. Par inválido (rede desconhecida, checksum inválido ou forma de outra rede) responde 400 KYT_WALLET_INVALID, com o campo novo error.reason igual a address_chain_mismatch, e nenhuma chamada externa é feita.
  • Valores em reais (volume_brl, direct_brl, indirect_brl, total_brl e value_brl) só existem com cotação completa. Os campos novos scope.brl_status (medido ou nao_cotado) e scope.brl_quote (moeda de origem, taxa em texto decimal, fonte e instante da cotação) dizem o estado. Em nao_cotado, nenhum campo em reais aparece no JSON, e o documento imprime NÃO COTADO, nunca R$ 0,00. Trate a ausência do campo como “não cotado”, e não como zero.
  • monitoring (status, source e observed_at) só aparece quando uma fonte comprova o estado de monitoramento da carteira nesta emissão. Sem ele, o documento imprime NÃO COMPROVADO NESTA EMISSÃO em vez de afirmar monitoramento contínuo.
  • attribution[].vasp passa a ser true, false ou null. Contraparte observada on-chain sem atribuição de entidade sai null (não determinado), e não mais false.
  • Na resposta do download, Content-Disposition traz o nome kyt-{perfil}-{emitido-em-UTC}-{report-id}.{formato} (attachment no PDF, inline no HTML e no JSON) e X-Report-Format traz html ou json (ausente no PDF). X-Report-Audit-Trail indica se a emissão entrou no histórico imutável. O CORS expõe Content-Disposition e X-Report-Format ao navegador.

Chaves de API:

  • Chaves emitidas a partir da 2.16.0 têm o formato snxc_<ambiente>_<identificador>_<segredo>, com identificador de 32 caracteres e segredo de 64 hexadecimais, 107 caracteres no total. Trate a chave como texto opaco. Chaves anteriores, snxc_<ambiente>_<hex>, continuam válidas até a expiração ou a revogação.
  • Se a plataforma não consegue verificar a chave, a resposta é 503 api_key_verification_unavailable, e a requisição pode ser repetida com espera. A chave nunca é tratada como válida sem verificação.
  • O cadastro e o provisionamento de tenant deixam de devolver chave de API (sandbox_api_key e api_key_temporary não são mais emitidos). A chave snxc_sand_ só existe no ambiente de teste e é emitida pela governança de chaves do painel, com MFA confirmada e Idempotency-Key.

Sandbox do desenvolvedor, em https://sandbox.api.crypto.sentinexrisk.com/api/v1:

  • O sandbox é um ambiente separado da produção e aceita somente chave com prefixo snxc_sand_, com validade de 30 dias. Credencial inexistente responde 401; credencial de outro ambiente, como uma snxc_live_, responde 403 api_key_environment_not_allowed, por ambiente incompatível, sem consultar o banco. A chave do sandbox também não funciona em produção.
  • O simulador responde só com as oito fixtures do catálogo sandbox-fixtures-v1 e não faz chamada externa. fx-kyt-low-v1 devolve ALLOW; fx-kyt-review-v1, REVIEW; fx-kyt-deny-sanctions-v1, DENY por um match sintético de sanção, a única base de DENY do catálogo; fx-provider-error-v1 e fx-provider-timeout-v1, HOLD com fail_closed verdadeiro e orientação de reprocessar. As três restantes exercitam o fluxo: fx-replay-v1 (repetição da mesma Idempotency-Key, mesmo resultado e nenhum efeito duplicado), fx-webhook-retry-v1 (duas falhas de entrega e sucesso na terceira tentativa) e fx-concurrent-idempotency-v1 (duas requisições concorrentes, um efeito confirmado).
  • O identificador público de cada fixture é derivado: os 20 primeiros bytes de SHA-256 sobre sentinex:sandbox-fixture:v1: seguido do fixture_id, em hexadecimal minúsculo com prefixo 0x, na rede ethereum. O catálogo, com os identificadores, os cenários e o resultado esperado de cada um, é publicado no portal do desenvolvedor (GET /sandbox/fixtures, com sessão). Os dois endereços reservados anteriores continuam respondendo o mesmo resultado, por compatibilidade, e não fazem parte do catálogo novo.
  • Endereço que não é fixture responde 422 SANDBOX_FIXTURE_NOT_SUPPORTED em POST /vasp/screenings, sem consulta de DNS nem de provider. O corpo desse 422 usa o mesmo envelope de erro da rota (error.code, error.http_status, error.message, error.action, error.retriable), com action apontando para /api/v1/sandbox/fixtures e o catálogo sandbox-fixtures-v1. Só o sandbox emite esse código.
  • Os limites do simulador são 60 requisições por minuto por chave, 300 por minuto por tenant e 10.000 por dia por tenant. Acima deles a resposta é 429.
  • Não há cobrança nem consulta livre à mainnet. O e-mail real usa credencial e subdomínio exclusivos do sandbox, limita-se a usuários do tenant e aos tipos autorizados. Os demais tipos ficam na caixa interna do tenant.

Contrato em pré-integração (info.x-snx-congelamento: pre-integracao). Nenhuma operação, campo ou enum muda; muda, de forma opcional, quando o misturador sai DENY.

  • O tenant pode escolher, no painel, bloquear automaticamente a proibição legal do misturador (Res. BCB nº 520/2025, art. 90, III, e parágrafo único). Com a escolha ligada, a contraparte misturador sai recommendation DENY, e reasons traz o motivo “política do cliente: proibição legal misturador”. O padrão é desligado: sem a escolha, o misturador continua saindo REVIEW com o rótulo da proibição, como na 8.1.1.
  • Sanção continua saindo DENY em qualquer configuração, e a escolha do tenant nunca a desliga.

Contrato em pré-integração (info.x-snx-congelamento: pre-integracao). Nenhuma operação, campo ou enum muda; muda o que DENY significa.

  • recommendation só sai DENY com base em sanção (contraparte em lista de sanções ou com categoria de sanção devolvida pelo provedor) ou por regra de bloqueio que o próprio tenant configurou no painel (limiar de score ou combinação de regras). Os demais riscos, como misturador, darknet, valor e velocidade, saem REVIEW. Antes, score a partir de 75 ou decisão de bloqueio do provedor sem sanção também saíam DENY.
  • Quando a recomendação é rebaixada, risk_level continua refletindo o risco medido (por exemplo CRITICAL), e reasons traz a decisão de risco original e o motivo. Contraparte misturador recebe também o motivo da proibição legal (Res. BCB nº 520/2025, art. 90, III).
  • Sandbox: a carteira de alto risco 0x00000000000000000000000000000000000000b2 é um misturador sem sanção e passa a devolver REVIEW com risk_level CRITICAL.
  • Laudo KYT (POST /vasp/kyt-report, formato JSON): carteira com match de sanções passa a sair com recommendation DENY em verdict e conclusion, igual ao screening da mesma carteira. Antes saía REVIEW.

Contrato em pré-integração (info.x-snx-congelamento: pre-integracao): ainda não há integrador em produção.

Esta versão tem mudança incompatível com a 8.0.1. Oito operações saíram do contrato. Isso só é permitido porque a linha ainda não tem integrador; depois do congelamento, uma remoção assim exigiria /api/v2. A política desta página também foi reescrita nesta versão, para registrar a fase de congelamento: até a 8.0.1 ela dizia que toda versão 8.x era compatível com a 8.0.0.

  • Saíram do contrato, e do que a API serve a uma integração só de KYT, as oito leituras de ledger, que devolviam listas vazias no produto KYT e agora respondem 404: GET /vasp/balances, GET /vasp/deposits/brl, GET /vasp/deposits/brl/{id}, GET /vasp/deposits/crypto, GET /vasp/deposits/crypto/{id}, GET /vasp/trades, GET /vasp/withdrawals e GET /vasp/withdrawals/{id}. O contrato passa de 30 para 22 operações.
  • Rota inexistente ou método errado sob /vasp, com chave válida, responde 404 com error.code route_not_found. Antes respondia 401 pedindo token de sessão. Sem chave válida, a resposta continua 401.
  • Quando a plataforma não consegue verificar a chave (falha interna na consulta), a resposta passa a ser 503 com error.code api_key_verification_unavailable, para repetir com backoff. Antes a mesma falha respondia 401 invalid_api_key, como se a chave fosse inválida.
  • O ambiente da chave passa a ser conferido antes do estado da conta: chave de ambiente não aceito recebe 403 de ambiente mesmo quando a conta também recusaria.
  • Erros: toda operação declara as respostas de erro que a API realmente devolve, cada uma com schema (401, 402, 403, 404, 429 com Retry-After, 500 e 503, e 413 nas que recebem corpo). Onde a mesma resposta tem mais de uma forma, o contrato lista as formas em anyOf. Correção de documentação: as formas já eram servidas; nada muda no fio.
  • Webhooks: alert.created passa a ser emitido, uma vez por alerta criado e só depois que o alerta é gravado. Assinatura com events vazio passa a receber também esse evento. alert.escalated segue aceito na assinatura e ainda não emitido.
  • Limites: depois da autenticação vale o limite do tenant, 2.000 requisições por minuto, com Retry-After de 1 a 60. O teto de 5.000 por hora por IP deixa de pesar sobre requisição autenticada, e a superfície /vasp deixa de pontuar User-Agent, cabeçalhos e trechos do corpo como ataque. Detalhes em Limites de requisição.
  • O contrato passa a declarar a fase da linha em info.x-snx-congelamento.

Correções de documentação: nada muda no que a API serve nem no que ela aceita.

  • A versão do contrato passa a ser própria: info.version deixa de acompanhar a especificação interna da plataforma e os SDKs, e só sobe quando este contrato muda.
  • Convenções: as listagens usam limit (padrão 50, teto 200); alertas e ledger paginam com offset, e clientes com o cursor cursor_before, devolvido em next_cursor. A versão anterior dizia que todas as listagens usavam offset.
  • GET /vasp/customers: a descrição passa a dizer como o cursor funciona. next_cursor volta vazio quando a página traz menos itens que limit; com a página cheia ele vem preenchido, e a página seguinte pode vir vazia.
  • Correção de documentação: GET /vasp/alerts sempre devolveu lista em data; o contrato agora descreve o que é servido. O schema da resposta passa a ser VASPAlertListResponse, com data como lista de VASPClientAlert e o bloco pagination (total, limit e offset), sem bloco meta. Quem tratava data como objeto seguia o contrato antigo, que não correspondia à resposta.
  • POST /vasp/kyt-report: o limite de 120 emissões por hora é contado por IP de origem, além do limite da chave.
  • POST /vasp/screenings: a descrição do header Idempotency-Key foi corrigida na acentuação, sem mudança de regra.
  • POST /vasp/webhooks: a descrição passa a trazer a situação de cada evento aceito em events numa integração de KYT: emitido, ou aceito na assinatura e ainda não emitido. Ela também diz que webhook.test chega só por POST /vasp/webhooks/{id}/test e não pode ser assinado. A lista aceita em events não mudou.

Primeira versão do contrato do cliente KYT, e a base do contrato v1. Versões anteriores da especificação descreviam a API inteira da plataforma e não eram contrato de cliente.

  • O contrato publicado passa a conter só a integração de KYT por chave de API: 30 operações em 26 caminhos de /vasp, em sete grupos (análise de transação, clientes, carteiras, ledger, alertas, webhooks e compliance).
  • Servidor único: https://api.crypto.sentinexrisk.com/api/v1.
  • Rotas de administração e do painel, autenticadas por sessão de usuário, não fazem parte do contrato.
  • POST /vasp/screenings devolve recommendation (ALLOW, REVIEW, HOLD ou DENY) e o bloco client_decision. A decisão operacional é do cliente e é registrada em PATCH /vasp/screenings/{id}/client-decision.
  • PATCH /vasp/screenings/{id}/decision está marcada como obsoleta (deprecated); use client-decision.
  • O contrato passa a ser publicado neste site, em /openapi.yaml e /openapi.json.