Appearance
POST /apiv2/screening
Solicite uma triagem de AML para um endereço de blockchain. Este é o contrato da versão 2: um único formato de resposta para cada provedor e cada estado da ordem, números decimais como strings e um formato de erro unificado.
Ele substitui o POST /apiv2/aml, que continua funcionando e não será descontinuado sem aviso prévio.
URL do endpoint
POST https://netts.io/apiv2/screeningCabeçalhos da requisição
| Header | Required | Description |
|---|---|---|
| Content-Type | Sim | application/json |
| X-API-KEY | Sim | Sua chave de API do painel da Netts |
| X-Idempotency-Key | Não | Sua própria chave para repetições seguras. Veja Idempotência |
Corpo da requisição
json
{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": true
}Parâmetros
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | Sim | Endereço a ser analisado, de 10 a 128 caracteres |
| network | string | Sim | Ticker da rede. Os tickers cobertos por cada provedor são listados por GET /apiv2/screening/providers; a tabela completa de redes com seus nomes está aqui |
| provider | string | Sim | elliptic ou bitok. Não há valor padrão |
| wait_for_result | boolean | Não | true aguarda o resultado por até 15 segundos. Padrão false |
| language | string | Não | Idioma do relatório. Apenas en |
Campos desconhecidos são rejeitados. Um corpo que contenha um campo não presente na tabela acima retorna 400 com o código 4001. Na versão 1, campos desconhecidos eram ignorados silenciosamente, e um erro de digitação em wait fazia com que o chamador esperasse por um resultado que nunca chegaria de forma síncrona.
provider é obrigatório e não possui valor padrão. Na versão 1, um provedor omitido significava Elliptic, de modo que um chamador que não escolhesse acabava pagando por um provedor que nunca especificou.
provider é uma string de formato livre no esquema, não uma enumeração. Hoje, dois valores são aceitos; a adição de um terceiro provedor não deve ser uma alteração drástica (breaking change) para qualquer pessoa que valide as respostas contra o esquema. A lista atual, as redes que cada provedor cobre e a escala em que cada um pontua vêm de GET /apiv2/screening/providers.
Examples de Requisições
cURL — aguardar pelo resultado
bash
curl -X POST https://netts.io/apiv2/screening \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-d '{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": true
}'cURL — aceitar e consultar status (polling)
bash
curl -X POST https://netts.io/apiv2/screening \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-d '{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "bitok"
}'A resposta é 202 Accepted com um cabeçalho Location apontando para a ordem.
Python
python
import requests
from decimal import Decimal
url = "https://netts.io/apiv2/screening"
headers = {
"Content-Type": "application/json",
"X-API-KEY": "your_api_key",
}
payload = {
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": True,
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code in (200, 202):
body = response.json()
print("Order:", body["order"]["client_order_id"])
print("Status:", body["order"]["status"])
if body["risk"]["score"] is not None:
# Parse provider numbers as Decimal, never as float
print("Score:", Decimal(body["risk"]["score"]), "of", body["risk"]["scale"]["max"])
print("Level:", body["risk"]["level"], "-", body["risk"]["level_source"])
print("Charged:", body["billing"]["charged_amount"], body["billing"]["charged_currency"])
else:
problem = response.json()
print(problem["code"], problem["title"], "-", problem["detail"])Códigos de Resposta
| Situação | Código | Headers |
|---|---|---|
| Ordem criada, triagem em execução em segundo plano | 202 Accepted | Location: /apiv2/screening/{client_order_id} |
O resultado está na resposta (wait_for_result) | 200 OK | — |
| Resultado reutilizado de uma checagem recente, nada cobrado | 200 OK | — |
| O endereço não possui atividade na blockchain, nada cobrado | 200 OK | — |
| Erro | consulte Erros | Content-Type: application/problem+json |
Respostas que contêm um resultado de triagem são enviadas com Cache-Control: private, no-store.
Resposta
json
{
"schema_version": 2,
"order": {
"client_order_id": "A90D21F68C9AEA2",
"status": "completed",
"api_version": "v2",
"cache_hit": false,
"created_at": "2026-09-13T08:14:29.614988Z",
"started_at": "2026-09-13T08:14:30.288251Z",
"completed_at": "2026-09-13T08:14:34.993174Z"
},
"request": {
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic"
},
"billing": {
"charged": true,
"price_usdt": "0.98",
"base_amount": "2.882421",
"markup_amount": "0",
"charged_amount": "2.882421",
"charged_currency": "TRX",
"exchange_rate": "0.33999200",
"payment_status": "pending"
},
"precheck": {
"activity_checked": true,
"activity_status": "active",
"source": "tron-address-checker"
},
"check": {
"provider": "elliptic",
"provider_check_id": "b4903a5d-9f22-4075-b51b-beb40b419b97",
"checked_at": "2026-09-13T08:14:32.144000Z",
"status": "completed",
"provider_status": "complete"
},
"risk": {
"score": "0.9634087310611608",
"scale": { "min": 0, "max": 10 },
"level": "low",
"level_source": "computed",
"provider_level": null,
"policy": "netts-risk-v1",
"by_direction": {
"source": "0.12332156899576921",
"destination": "0.9634087310611608"
}
},
"sanctions": {
"self": false,
"self_entities": null,
"exposure": {
"entity": "HTX (formerly Huobi) - Post-Sanctions - UK Sanctions List - 26 May 2026",
"category": "Exchange",
"share_fraction": "0.004409451392423414",
"proximity": "indirect",
"hops": 3,
"direction": "source",
"rule_name": "Sanctioned, TF & CSAM"
},
"items": []
},
"exposure": [
{
"category": "Exchange",
"share_fraction": "0.1888065152442461",
"direction": "source",
"hops": 2,
"is_screened_address": false,
"entities": [
{ "name": "Binance", "category": "Exchange", "is_vasp": true,
"is_primary": true, "is_after_sanction_date": null }
]
}
],
"rules": [
{ "name": "Sanctioned, TF & CSAM", "type": "exposure", "level": null,
"score": "0.061426483114820525", "share_fraction": null, "category": null,
"proximity": null, "direction": "source", "detected_at": null }
],
"entities": [
{ "name": "Unknown", "category": "Unknown", "is_vasp": null,
"is_primary": true, "is_after_sanction_date": false }
],
"primary_entity": {
"name": "Unknown", "category": "Unknown", "is_vasp": null,
"is_primary": true, "is_after_sanction_date": false
},
"sanctioned_entities": [],
"wallet": {
"inflow_usd": "354663.6116669158",
"outflow_usd": "80248.63081808022"
},
"provider_data": { }
}O conjunto de campos nunca muda
Cada bloco listado acima está presente em todas as respostas, independentemente do provedor e do estado da ordem. O que um provedor não fornece fica como null; uma lista vazia é [], não null; um bloco que ainda não possui dados é preenchido com valores nulos em vez de ser omitido. Um único parser lida com uma checagem recém-aceita e com a mesma checagem após sua conclusão.
Duas consequências para o seu código:
- ignore campos que você não conhece. Novos campos são adicionados a estes blocos sem uma nova versão. Rejeitar um campo desconhecido é um bug do seu lado, não nosso;
provider_datanão faz parte do contrato. Sua estrutura acompanha o provedor e muda quando o provedor muda. Tudo o que o contrato garante está nos blocos acima.
order
| Field | Type | Description |
|---|---|---|
| client_order_id | string | Identificador da ordem, usado para consultar o resultado posteriormente |
| status | string | pending, processing, completed, skipped, failed |
| api_version | string | Contrato que criou a ordem |
| cache_hit | boolean | true quando um resultado recente foi reutilizado e nada foi cobrado |
| created_at | string | RFC 3339, UTC, microssegundos |
| started_at | string | null | Quando a chamada ao provedor foi iniciada. null para skipped |
| completed_at | string | null | Quando o resultado chegou |
| error | string | Apenas para failed: motivo da falha |
| reason | string | Apenas para skipped: address_inactive |
Todos os timestamps estão em UTC, RFC 3339, com sufixo Z e precisão de microssegundos.
billing
| Field | Type | Description |
|---|---|---|
| charged | boolean | Se houve cobrança de valores |
| price_usdt | string | Preço de tabela do provedor em USDT |
| base_amount | string | Preço da ordem na moeda cobrada, sem margem de subusuário |
| markup_amount | string | Margem de subusuário. "0" para uma conta direta |
| charged_amount | string | Valor efetivamente debitado do saldo |
| charged_currency | string | TRX |
| exchange_rate | string | null | Taxa utilizada para a conversão |
| payment_status | string | paid, pending, failed, not_charged |
payment_status fica como pending por um breve período após uma checagem bem-sucedida: a cobrança é retida primeiro e liquidada dentro de uma hora. failed significa que o valor foi estornado. not_charged significa que nenhuma cobrança foi gerada — um resultado reutilizado ou um endereço ignorado.
precheck
Antes de uma triagem paga, o endereço é verificado quanto a atividade na blockchain. Um endereço sem atividade não é enviado ao provedor e não é cobrado.
| Field | Type | Description |
|---|---|---|
| activity_checked | boolean | Se a verificação foi executada. false em redes onde ela não existe |
| activity_status | string | active, inactive, unknown |
| source | string | null | Nome do mecanismo |
unknown não impede a triagem paga: se o serviço de verificação de atividade estiver indisponível, o endereço é tratado como ativo.
check
| Field | Type | Description |
|---|---|---|
| provider | string | Provedor que realizou a checagem |
| provider_check_id | string | null | O identificador próprio do provedor — mencione-o ao contestar um resultado com eles |
| checked_at | string | null | Quando o provedor produziu o resultado |
| status | string | Veja a tabela abaixo |
| provider_status | string | null | A redação original do próprio provedor, sem alterações |
order.status | check.status | Meaning |
|---|---|---|
pending | pending | Ordem aceita, ainda não iniciada |
processing | running | O provedor está processando |
completed | completed | Resultado recebido |
failed | failed | Recusado antes ou durante a chamada ao provedor |
skipped | not_performed | O endereço não possui atividade; o provedor nunca foi chamado e nada foi cobrado |
risk
| Field | Type | Description |
|---|---|---|
| score | string | null | A pontuação original do provedor, como uma string decimal |
| scale | object | min e max da escala daquele provedor |
| level | string | none, low, medium, high, severe |
| level_source | string | computed — derivamos o nível a partir da pontuação; provider — o provedor informou diretamente |
| provider_level | string | null | O termo utilizado pelo próprio provedor, quando ele o retorna |
| policy | string | Nome da política de limites, netts-risk-v1 |
| by_direction | object | Pontuação dividida em source e destination, quando o provedor a divide |
A pontuação nunca é recalculada em outra escala. A Elliptic opera de 0 a 10 e a BitOK opera de 0 a 1, e 7 em uma escala não equivale a 0.7 na outra em nenhum sentido prático. A escala vem na resposta para que uma integração desenvolvida para um provedor não interprete erroneamente outro após uma simples alteração de configuração.
O nível utiliza um vocabulário único em toda a API. Onde o provedor define um nível próprio, nós o repassamos e indicamos isso em level_source; onde não define, derivamos o nível a partir da pontuação com os limites de netts-risk-v1 e indicamos essa condição. A mesma palavra aparece na resposta da API, no painel e no relatório em PDF para a mesma checagem.
exposure[], rules[], entities[]
exposure[] detalha os fundos por categoria de contraparte. rules[] lista as regras do provedor que foram acionadas. entities[] lista as entidades às quais o próprio endereço pertence; primary_entity seleciona uma delas seguindo uma regra fixa — a entidade que o provedor marcou como primária, caso contrário a primeira, caso contrário null. sanctioned_entities[] contém as entidades de entities[] sinalizadas como ativas após a data de sanção.
Proporções são frações, nunca porcentagens
Cada proporção na resposta é um campo único, share_fraction, uma string decimal entre "0" e "1".
text
Elliptic reports 31.574732212596924 % -> "share_fraction": "0.31574732212596924"
BitOK reports 0.8488 -> "share_fraction": "0.8488"Os provedores divergem nas unidades: o mesmo terço de exposição chega como 31.57 de um e 0.3157 do outro. Um único campo comportando ambos seria impossível de interpretar sem conhecer o provedor. O valor original do provedor em suas próprias unidades permanece em provider_data.
Números são strings
Todos os números fornecidos por um provedor — pontuações, proporções, volumes em USD e todos os valores em billing — são strings decimais.
json
"score": "0.9634087310611608"Fazer o parse disso como um número JSON em JavaScript, Go ou qualquer outra linguagem com ponto flutuante binário resulta em uma aproximação, e o valor exibido deixa de corresponder ao valor emitido pelo provedor. Faça o parse desses campos com um tipo decimal: Decimal em Python, BigDecimal em Java, decimal.Decimal ou string em JavaScript.
Campos que são nossos e não do provedor — scale.min, scale.max, hops — são números JSON comuns.
Reutilizando um resultado recente
Quando você analisa o mesmo endereço, rede e provedor novamente dentro de 60 segundos, o resultado anterior é retornado e nada é cobrado.
Cada requisição ainda cria sua própria ordem com seu respectivo client_order_id; a ordem reutilizada é marcada com "cache_hit": true e seu bloco billing informa "charged": false com "payment_status": "not_charged". O identificador da ordem de onde o resultado se originou não é divulgado — ele pode pertencer a outra conta.
A reutilização só ocorre dentro de uma mesma conta. Um resultado analisado por outra pessoa nunca será retornado para você.
Idempotency
Envie X-Idempotency-Key com um valor próprio para tornar uma nova tentativa segura: a mesma chave com o mesmo corpo retorna a resposta armazenada em vez de solicitar uma segunda checagem.
| Situação | Código | Response |
|---|---|---|
| A primeira requisição com esta chave ainda está em execução | 409 | 4090 |
| A mesma chave, um corpo de requisição diferente | 409 | 4093 |
| A mesma chave, o mesmo corpo, já finalizada | o código armazenado | a resposta armazenada |
Se você não enviar o cabeçalho, uma chave será gerada automaticamente para você a partir da chave de API, do endereço, do provedor e do seu endereço IP, em uma janela de dois segundos. Isso protege contra cliques duplos e repetições de gateway, mas não contra uma repetição um minuto depois: esta última será uma ordem nova autêntica e será cobrada.
As chaves têm escopo definido por endpoint. O mesmo valor enviado para POST /apiv2/aml e para este endpoint representa duas garantias independentes sobre duas requisições diferentes — os corpos diferem, assim como as respostas. Reutilizar sua chave durante a migração de uma integração da versão 1 para a versão 2 é seguro: isso não retorna uma resposta da versão 1 nem é considerado como a mesma chave usada com um corpo diferente.
Respostas de erro
Todo erro gerado pela aplicação utiliza o padrão RFC 9457 com Content-Type: application/problem+json:
json
{
"type": "https://doc.netts.io/api/v2/errors/insufficient-funds",
"title": "Insufficient funds",
"status": 403,
"detail": "Insufficient funds. Required: 2.888742 TRX, Available: 1.203000 TRX",
"instance": "/apiv2/screening",
"code": 1004,
"required_trx": "2.888742",
"available_trx": "1.203000"
}type, title, status, detail e instance são os campos padrão. O code numérico é mantido como uma extensão para que integrações desenvolvidas para a versão 1 possam continuar correspondendo por ele. Campos adicionais dependem do erro e devem ser ignorados caso você não os conheça.
| Código | HTTP | Meaning |
|---|---|---|
4000 | 400 | O corpo não é um JSON válido |
4001 | 400 | Um campo falhou na validação ou um campo desconhecido foi enviado |
4002 | 403 | O provedor não está disponível para a sua conta |
4003 | 400 | Identificador de ordem malformado |
4004 | 400 | O provedor não suporta a rede solicitada |
4010 | 401 | Chave de API ausente |
4011 | 401 | Chave de API ou endereço IP não aceitos |
4040 | 404 | Ordem não encontrada |
4041 | 404 | Conta não encontrada |
4090 | 409 | Uma requisição com esta chave de idempotência ainda está em execução |
4091 | 409 | Requisição duplicada |
4093 | 409 | Esta chave de idempotência foi usada com um corpo diferente |
1004 | 403 | Saldo insuficiente |
5000 | 500 | Erro interno |
5001 | 500 | A cobrança não foi processada |
5002 | 500 | A ordem não foi criada |
5030 | 503 | O provedor está indisponível |
Erros que não usam este formato
Algumas falhas ocorrem no gateway, antes de atingir a aplicação, e mantêm o formato próprio do gateway. Trate qualquer resposta cujo Content-Type não seja application/problem+json como uma destas:
| Situação | HTTP | Body |
|---|---|---|
| Chave de API ausente ou não aceita | 401 | {"detail":{"code":-1,"msg":"Invalid or missing API key"}} |
| Limite de taxa excedido | 429 | {"message":"API rate limit exceeded"} |
| Caminho desconhecido ou método não atendido pela rota | 404 / 405 | {"detail":"Method Not Allowed"} |
Limites de taxa
O limite é compartilhado com o POST /apiv2/aml e as demais rotas de AML: 5 requisições por segundo e 150 por minuto. A migração para este endpoint não concede uma cota adicional.
Notas
- Preços: Elliptic $0.98, BitOK $0.50 por checagem, cobrados do saldo em TRX com base na taxa no momento da cobrança.
- Tempo de processamento: a maioria das checagens termina em poucos segundos; um endereço com longo histórico pode levar até três minutos. Utilize o modo assíncrono e consulte o resultado com GET /apiv2/screening/{client_order_id}.
- Endereços inativos retornam
skippede não são cobrados. - A resposta bruta do provedor nunca é retornada.
provider_dataé uma projeção revisada; campos pertencentes à nossa conta com o provedor, e não ao endereço analisado, não são divulgados a ninguém.
Relatórios
O endpoint retorna JSON e nada mais. Não há PDF e não há Markdown.
Tudo o que compõe um relatório já está presente na resposta: o bloco unificado e provider_data. Renderizá-lo do seu lado permite gerar o documento que você realmente deseja — sua marca, seu idioma, seu layout — o que é particularmente relevante se você revende checagens, pois um relatório com o nosso nome não é o documento correto para entregar ao seu próprio cliente.
Se você precisa de um relatório como comprovante para terceiros — um banco, um regulador, uma contraparte — observe que um PDF sem assinatura digital não serve como prova, independentemente de quem o gere: ele pode ser editado em um editor de texto em um minuto. Um artefato verificável requer uma assinatura ou uma página pública de validação, e essa é uma funcionalidade diferente. Se este for o seu caso, informe-nos sobre as exigências da sua contraparte.
Relatórios em PDF legíveis por humanos existem para as mesmas checagens no painel da Netts, em dezessete idiomas.
Consulte também
- GET /apiv2/screening/{client_order_id} — consultar uma checagem
- GET /apiv2/screening/history — suas checagens, com paginação por cursor
- GET /apiv2/screening/providers — provedores, preços, redes, escalas
- GET /apiv2/screening/price — preço de um provedor
- Sanções em um resultado de AML — o que
sanctionsindica e o que a flag do provedor indica