POST /apiv2/screening
Solicite una verificación AML para una dirección de blockchain. Este es el contrato de la versión 2: una única estructura de respuesta para cada proveedor y cada estado de la orden, números decimales como cadenas de texto y un formato de error único.
Reemplaza a POST /apiv2/aml, que sigue funcionando y no se retirará sin previo aviso.
URL del endpoint
POST https://netts.io/apiv2/screeningEncabezados de solicitud
| Header | Required | Description |
|---|---|---|
| Content-Type | Sí | application/json |
| X-API-KEY | Sí | Su clave de API del panel de control de Netts |
| X-Idempotency-Key | No | Su propia clave para reintentos seguros. Consulte Idempotencia |
Cuerpo de la solicitud
{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": true
}Parámetros
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | Sí | Dirección a verificar, de 10 a 128 caracteres |
| network | string | Sí | Ticker de la red. Los tickers que cubre cada proveedor se enumeran en GET /apiv2/screening/providers; la tabla completa de redes con sus nombres está aquí |
| provider | string | Sí | elliptic o bitok. No hay un valor predeterminado |
| wait_for_result | boolean | No | true espera el resultado durante un máximo de 15 segundos. Valor predeterminado false |
| language | string | No | Idioma del informe. Solo en |
Los campos desconocidos se rechazan. Un cuerpo que contenga un campo que no esté en la tabla anterior devuelve 400 con el código 4001. En la versión 1, los campos desconocidos se ignoraban silenciosamente, y un error tipográfico en wait significaba que el emisor de la llamada esperaba un resultado que nunca llegaría de forma síncrona.
provider es obligatorio y no tiene valor predeterminado. En la versión 1, omitir el proveedor significaba Elliptic, por lo que un emisor que no elegía pagaba por un proveedor que nunca había especificado.
provider es una cadena de texto libre en el esquema, no una enumeración. En la actualidad se aceptan dos valores; un tercer proveedor no debe suponer un cambio disruptivo para nadie que valide las respuestas con el esquema. La lista actual, las redes que cubre cada proveedor y la escala en la que puntúa cada uno provienen de GET /apiv2/screening/providers.
Ejemplos
cURL — esperar el resultado
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 — aceptar y consultar periódicamente
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"
}'La respuesta es 202 Accepted con un encabezado Location que apunta a la orden.
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 respuesta
| Situation | Code | Headers |
|---|---|---|
| Orden creada, la verificación se ejecuta en segundo plano | 202 Accepted | Location: /apiv2/screening/{client_order_id} |
El resultado está en la respuesta (wait_for_result) | 200 OK | — |
| Resultado reutilizado de una verificación reciente, no se cobró nada | 200 OK | — |
| La dirección no tiene actividad en la blockchain, no se cobró nada | 200 OK | — |
| Error | consulte Errores | Content-Type: application/problem+json |
Las respuestas que contienen un resultado de verificación se envían con Cache-Control: private, no-store.
Respuesta
{
"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": { }
}El conjunto de campos nunca cambia
Cada bloque listado anteriormente está presente en cada respuesta, sin importar el proveedor ni el estado de la orden. Lo que un proveedor no suministra es null; una lista que no contiene nada es [], no null; un bloque que aún no tiene datos se rellena con valores nulos en lugar de omitirse. Un único analizador procesa una verificación que acaba de ser aceptada y la misma verificación una vez finalizada.
Dos consecuencias para su código:
- ignore los campos que no conozca. Se agregan nuevos campos a estos bloques sin una nueva versión. Rechazar un campo desconocido es un error de su parte, no nuestro;
provider_datano forma parte del contrato. Su estructura sigue al proveedor, y cambia cuando este cambia. Todo lo que garantiza el contrato reside en los bloques anteriores.
order
| Field | Type | Description |
|---|---|---|
| client_order_id | string | Identificador de la orden, utilizado para leer el resultado más tarde |
| status | string | pending, processing, completed, skipped, failed |
| api_version | string | Contrato que creó la orden |
| cache_hit | boolean | true cuando se reutilizó un resultado reciente y no se cobró nada |
| created_at | string | RFC 3339, UTC, microsegundos |
| started_at | string | null | Momento en el que comenzó la llamada al proveedor. null para skipped |
| completed_at | string | null | Momento en el que llegó el resultado |
| error | string | Solo para failed: motivo por el cual falló |
| reason | string | Solo para skipped: address_inactive |
Todas las marcas de tiempo son UTC, RFC 3339, con sufijo Z y precisión de microsegundos.
billing
| Field | Type | Description |
|---|---|---|
| charged | boolean | Si se dedujo dinero |
| price_usdt | string | Precio de lista del proveedor en USDT |
| base_amount | string | Precio de la orden en la moneda cobrada, sin margen de subusuario |
| markup_amount | string | Margen del subusuario. "0" para una cuenta directa |
| charged_amount | string | Lo que se dedujo efectivamente del saldo |
| charged_currency | string | TRX |
| exchange_rate | string | null | Tasa utilizada para la conversión |
| payment_status | string | paid, pending, failed, not_charged |
payment_status permanece en pending durante un breve período tras una verificación exitosa: el cargo se retiene primero y se liquida dentro de la hora. failed significa que el dinero fue reembolsado. not_charged significa que nunca se generó ningún cargo — un resultado reutilizado o una dirección omitida.
precheck
Antes de una verificación paga, se comprueba la actividad de la dirección en la blockchain. Una dirección sin actividad no se envía al proveedor y no se le cobra.
| Field | Type | Description |
|---|---|---|
| activity_checked | boolean | Si la verificación se ejecutó. false en redes donde no existe |
| activity_status | string | active, inactive, unknown |
| source | string | null | Nombre del mecanismo |
unknown no detiene la verificación paga: si el servicio de actividad no está disponible, la dirección se trata como activa.
check
| Field | Type | Description |
|---|---|---|
| provider | string | Proveedor que realizó la verificación |
| provider_check_id | string | null | El identificador propio del proveedor — indíquelo si disputa un resultado con ellos |
| checked_at | string | null | Momento en el que el proveedor generó el resultado |
| status | string | Consulte la tabla a continuación |
| provider_status | string | null | La redacción propia del proveedor, sin modificaciones |
order.status | check.status | Meaning |
|---|---|---|
pending | pending | Orden aceptada, aún no iniciada |
processing | running | El proveedor está trabajando en ello |
completed | completed | Resultado recibido |
failed | failed | Rechazada antes o durante la llamada al proveedor |
skipped | not_performed | La dirección no tiene actividad; nunca se llamó al proveedor y no se cobró nada |
risk
| Field | Type | Description |
|---|---|---|
| score | string | null | La puntuación propia del proveedor, como una cadena decimal |
| scale | object | min y max de la escala de ese proveedor |
| level | string | none, low, medium, high, severe |
| level_source | string | computed — derivamos el nivel a partir de la puntuación; provider — el proveedor lo indicó |
| provider_level | string | null | La palabra propia del proveedor, cuando devuelve una |
| policy | string | Nombre de la política de umbrales, netts-risk-v1 |
| by_direction | object | Puntuación dividida en source y destination, cuando el proveedor la divide |
La puntuación nunca se reescala. Elliptic opera de 0 a 10 y BitOK de 0 a 1, y 7 en una escala no equivale en ningún sentido significativo a 0.7 en la otra. La escala se incluye en la respuesta para que una integración desarrollada para un proveedor no interprete erróneamente a otro tras un simple cambio de configuración.
El nivel utiliza un único vocabulario en toda la API. Cuando el proveedor indica un nivel propio, lo transmitimos directamente y lo señalamos en level_source; cuando no lo hace, derivamos el nivel a partir de la puntuación con los umbrales de netts-risk-v1 y lo indicamos en su lugar. La misma palabra aparece en la respuesta de la API, el panel de control y el informe en PDF para la misma verificación.
exposure[], rules[], entities[]
exposure[] desglosa los fondos por categoría de contraparte. rules[] enumera las reglas del proveedor que se activaron. entities[] lista las entidades a las que pertenece la propia dirección; primary_entity selecciona una de ellas mediante una regla fija — la entidad que el proveedor marcó como principal; en su defecto, la primera; si no hay ninguna, null. sanctioned_entities[] contiene aquellas entidades de entities[] que están marcadas como activas después de una fecha de sanciones.
Las participaciones son fracciones, nunca porcentajes
Cada participación en la respuesta es un campo único, share_fraction, una cadena decimal entre "0" y "1".
Elliptic reports 31.574732212596924 % -> "share_fraction": "0.31574732212596924"
BitOK reports 0.8488 -> "share_fraction": "0.8488"Los proveedores difieren en las unidades: el mismo tercio de exposición llega como 31.57 desde uno y como 0.3157 desde el otro. Sería imposible interpretar un único campo que contuviera ambos sin conocer al proveedor. El número original del proveedor en sus propias unidades se conserva en provider_data.
Los números son cadenas de texto
Cada número procedente de un proveedor — puntuaciones, participaciones, volúmenes en USD y cada importe en billing — es una cadena decimal.
"score": "0.9634087310611608"Analizarlo como un número JSON en JavaScript, Go o cualquier otro lenguaje con coma flotante binaria produce una aproximación, y el valor que se imprime deja de coincidir con el valor emitido por el proveedor. Analice estos campos con un tipo decimal: Decimal en Python, BigDecimal en Java, decimal.Decimal o una cadena en JavaScript.
Los campos que son nuestros y no del proveedor — scale.min, scale.max, hops — son números JSON comunes.
Reutilización de un resultado reciente
Cuando verifica la misma dirección, red y proveedor nuevamente dentro de 60 segundos, se devuelve el resultado anterior y no se cobra nada.
Cada solicitud sigue creando su propia orden con su propio client_order_id; la reutilizada se marca con "cache_hit": true y su bloque billing reporta "charged": false con "payment_status": "not_charged". El identificador de la orden de la que provino el resultado no se revela — puede pertenecer a otra cuenta.
La reutilización solo ocurre dentro de una misma cuenta. Un resultado verificado por otra persona nunca se le devolverá a usted.
Idempotencia
Envíe X-Idempotency-Key con un valor propio para que el reintento sea seguro: la misma clave con el mismo cuerpo devuelve la respuesta almacenada en lugar de solicitar una segunda verificación.
| Situation | Code | Response |
|---|---|---|
| La primera solicitud con esta clave todavía se está ejecutando | 409 | 4090 |
| La misma clave, un cuerpo de solicitud diferente | 409 | 4093 |
| La misma clave, el mismo cuerpo, ya finalizada | el código almacenado | la respuesta almacenada |
Si no envía el encabezado, se genera una clave automáticamente a partir de la clave de API, la dirección, el proveedor y su dirección IP, dentro de una ventana de dos segundos. Esto protege contra un doble clic y un reintento de la pasarela, pero no contra una repetición un minuto después: esta última constituye una orden nueva genuina y se cobra.
El alcance de las claves es por endpoint. El mismo valor enviado a POST /apiv2/aml y a este endpoint representa dos promesas independientes sobre dos solicitudes diferentes — los cuerpos difieren, al igual que las respuestas. Reutilizar su clave mientras migra una integración de la versión 1 a la versión 2 es seguro: no le devolverá una respuesta de la versión 1 ni se considerará la misma clave utilizada con un cuerpo diferente.
Respuestas de error
Cada error generado por la aplicación utiliza RFC 9457 con Content-Type: application/problem+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 son los campos estándar. El code numérico se mantiene como una extensión para que las integraciones desarrolladas para la versión 1 puedan seguir comparándolo. Los campos adicionales dependen del error y deben ignorarse cuando no los reconozca.
| Code | HTTP | Meaning |
|---|---|---|
4000 | 400 | El cuerpo no es un JSON válido |
4001 | 400 | Un campo no superó la validación o se envió un campo desconocido |
4002 | 403 | El proveedor no está disponible para su cuenta |
4003 | 400 | Identificador de orden con formato incorrecto |
4004 | 400 | El proveedor no admite la red solicitada |
4010 | 401 | Falta la clave de API |
4011 | 401 | Clave de API o dirección IP no aceptada |
4040 | 404 | Orden no encontrada |
4041 | 404 | Cuenta no encontrada |
4090 | 409 | Una solicitud con esta clave de idempotencia aún se está ejecutando |
4091 | 409 | Solicitud duplicada |
4093 | 409 | Esta clave de idempotencia se utilizó con un cuerpo diferente |
1004 | 403 | Saldo insuficiente |
5000 | 500 | Error interno |
5001 | 500 | El cobro no se completó |
5002 | 500 | La orden no fue creada |
5030 | 503 | El proveedor no está disponible |
Errores que no utilizan este formato
Algunos fallos ocurren en la pasarela, antes de llegar a la aplicación, y conservan la estructura propia de la pasarela. Considere cualquier respuesta cuyo Content-Type no sea application/problem+json como uno de estos casos:
| Situation | HTTP | Body |
|---|---|---|
| Sin clave de API o clave no aceptada | 401 | {"detail":{"code":-1,"msg":"Invalid or missing API key"}} |
| Límite de tasa excedido | 429 | {"message":"API rate limit exceeded"} |
| Ruta desconocida o método no admitido por la ruta | 404 / 405 | {"detail":"Method Not Allowed"} |
Límites de tasa
El límite se comparte con POST /apiv2/aml y las demás rutas de AML: 5 solicitudes por segundo y 150 por minuto. Migrar a este endpoint no le otorga una cuota adicional.
Notas
- Precios: Elliptic $0.98, BitOK $0.50 por verificación, cobrados del saldo en TRX a la tasa vigente al momento del cobro.
- Tiempo de procesamiento: la mayoría de las verificaciones finalizan en unos pocos segundos; una dirección con un historial extenso puede tardar hasta tres minutos. Utilice el modo asíncrono y lea el resultado con GET /apiv2/screening/{client_order_id}.
- Las direcciones inactivas devuelven
skippedy no se cobran. - La respuesta sin procesar del proveedor nunca se devuelve.
provider_dataes una proyección revisada; los campos que pertenecen a nuestra cuenta con el proveedor y no a la dirección verificada no se publican a nadie.
Informes
El endpoint devuelve JSON y nada más. No hay PDF ni Markdown.
Todo de lo que se compone un informe ya está en la respuesta: el bloque unificado y provider_data. Renderizarlo por su cuenta le proporciona el documento que realmente necesita — su marca, su idioma, su diseño — lo cual es especialmente relevante si revende verificaciones, ya que un informe con nuestro nombre no es el documento adecuado para entregar a su propio cliente.
Si necesita un informe como comprobante para un tercero — un banco, un regulador, una contraparte — tenga en cuenta que un PDF sin firmar no constituye una prueba, independientemente de quién lo genere: puede editarse en un editor de texto en un minuto. Un archivo verificable necesita una firma o una página de verificación pública, y esa es una funcionalidad diferente. Si este es su caso, indíquenos los requisitos de su contraparte.
Existen informes en PDF legibles para humanos de las mismas verificaciones en el panel de control de Netts, en diecisiete idiomas.
Véase también
- GET /apiv2/screening/{client_order_id} — consultar una verificación
- GET /apiv2/screening/history — sus verificaciones, paginadas mediante cursor
- GET /apiv2/screening/providers — proveedores, precios, redes, escalas
- GET /apiv2/screening/price — precio de un proveedor
- Sanciones en un resultado AML — lo que indica
sanctionsy lo que indica el indicador del proveedor