Migración desde AMLBot
Si tu servicio ya verifica direcciones y transacciones con la API de AMLBot, puedes pasar a Leeri sin tocar su código de solicitudes. Leeri expone un endpoint compatible con AMLBot que reproduce el contrato de comunicación campo por campo: las mismas solicitudes en formato de formulario, el mismo token MD5, el mismo sobre de respuesta y el mismo ciclo de pending y recheck. Esta página describe ese contrato tal como lo implementa Leeri.
Cambia en un día
Cambian tres variables de entorno y nada más.
| Variable | Cámbiala a |
|---|---|
AMLBOT_URL | https://api.leeri.ai/compat/amlbot |
AMLBOT_ID | El accessId de tu credencial compat. |
AMLBOT_KEY | El secreto de la misma credencial compat. |
La construcción de solicitudes, el cálculo del token, la codificación del formulario, los nombres de los campos y tu bucle de pending siguen exactamente como están.
Leeri emite una credencial compat junto con tu cuenta: escribe a hello@leeri.ai. Es independiente de las claves ak_ de la API nativa, y el motivo es técnico. El protocolo exige que el servidor recalcule el token MD5 a partir del secreto en texto claro, mientras que una clave de API se almacena de forma irreversible y no puede recuperarse. Una credencial compat tiene su propio accessId, su propio secreto y su propia rotación. Durante una rotación se aceptan durante un tiempo tanto el secreto antiguo como el nuevo, de modo que pasar tu servicio al nuevo es un cambio de configuración y no una interrupción.
Una credencial compat pertenece a un entorno, sandbox o live, igual que una clave de API. Empieza con una credencial sandbox: consulta Probar el bucle de pending.
Solicitudes
Ambos tipos de solicitud son un POST con Content-Type: application/x-www-form-urlencoded a la misma URL. Los campos presentes deciden de cuál se trata.
| Campo | Verificación de dirección | Verificación de transacción |
|---|---|---|
accessId | obligatorio | obligatorio |
token | obligatorio | obligatorio |
hash | la dirección a cribar | el hash de la transacción |
asset | obligatorio | obligatorio |
address | no se envía | la dirección de la transacción a cribar |
direction | no se envía | deposit o withdrawal |
asset es un símbolo exactamente como aparece en Activos admitidos. Los campos desconocidos se rechazan. direction acepta el valor abreviado withdrawa que envían algunos clientes y lo lee como withdrawal.
El token
token = MD5("{hash}:{key}:{accessId}")
hash es el valor del campo hash que envías, key es tu secreto compat y accessId es tu accessId compat. El resultado es el resumen hexadecimal de 32 caracteres en minúsculas.
El token se calcula una sola vez, a partir de la solicitud de verificación original, y se reenvía sin cambios en cada recheck. Nunca se recalcula a partir del uid de la verificación. Tu cliente ya se comporta así; no lo cambies.
- curl
- Node.js
- Python
TOKEN=$(printf '%s' "$ADDRESS:$AMLBOT_KEY:$AMLBOT_ID" | openssl md5 | awk '{print $NF}')
curl -s -X POST "$AMLBOT_URL" \
--data-urlencode "accessId=$AMLBOT_ID" \
--data-urlencode "token=$TOKEN" \
--data-urlencode "hash=$ADDRESS" \
--data-urlencode "asset=TRX"
import { createHash } from 'node:crypto';
const { AMLBOT_URL, AMLBOT_ID, AMLBOT_KEY } = process.env;
const token = createHash('md5').update(`${address}:${AMLBOT_KEY}:${AMLBOT_ID}`).digest('hex');
const response = await fetch(AMLBOT_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ accessId: AMLBOT_ID, token, hash: address, asset: 'TRX' }),
});
const body = await response.json();
import hashlib
import os
import requests
url = os.environ["AMLBOT_URL"]
access_id = os.environ["AMLBOT_ID"]
key = os.environ["AMLBOT_KEY"]
token = hashlib.md5(f"{address}:{key}:{access_id}".encode()).hexdigest()
response = requests.post(
url,
data={"accessId": access_id, "token": token, "hash": address, "asset": "TRX"},
timeout=30,
)
body = response.json()
Para una verificación de transacción, envía el hash de la transacción como hash y añade address y direction. El token sigue siendo MD5("{hash}:{key}:{accessId}") con ese hash de transacción.
Respuestas
Solo result y balance están en el nivel superior. La carga útil va anidada en data.
{
"result": true,
"balance": 4210,
"data": {
"status": "success",
"uid": "5b0c3f64-1a52-4c8e-9f3e-2f6c1d7a9e10",
"riskscore": 0.74,
"signals": {
"atm": 0,
"child_exploitation": 0,
"dark_market": 0,
"dark_service": 0,
"decentralized_exchange_contract": 0,
"enforcement_action": 0,
"exchange": 0.05,
"exchange_fraudulent": 0,
"gambling": 0,
"illegal_service": 0,
"infrastructure_as_a_service": 0,
"liquidity_pools": 0,
"malware": 0,
"marketplace": 0,
"merchant_services": 0,
"miner": 0,
"mixer": 0.12,
"other": 0,
"p2p_exchange": 0,
"p2p_exchange_mlrisk_high": 0,
"payment": 0,
"ransom": 0,
"risky_exchange": 0,
"sanctions": 0,
"scam": 0,
"seized_assets": 0,
"stolen_coins": 0,
"terrorism_financing": 0,
"unnamed_service": 0.31,
"wallet": 0,
"unnamed_entity_low_risk": 0,
"unnamed_entity_medium_risk": 0,
"unnamed_entity_high_risk": 0
},
"address": "TEXAMPLEexampleEXAMPLEexample00000",
"fiat_code_effective": "USD",
"counterparty": null,
"reportedAddressBalance": null,
"addressDetailsData": null,
"blackListsConnections": false,
"hasBlackListFlag": false,
"pdfReport": "https://…",
"memo": null,
"asset": "TRX",
"network": "tron",
"timestamp": "2026-10-27 09:14:04"
}
}
Los valores son ilustrativos. Una verificación de transacción responde con los mismos campos y añade amount, risky_volume, risky_volume_fiat, fiat, direction y tx.
Los cuatro campos que usa tu control de flujo
| Campo | Rama que determina |
|---|---|
result | Éxito (true) o fallo (false). |
data.status | Respuesta final o todavía pending. |
balance | La rama de saldo agotado. |
data.uid | Lo que consulta el recheck. |
Todos los demás campos son datos para mostrar.
Estado
data.status | Significado |
|---|---|
success | La verificación es definitiva y data lleva el resultado. |
pending | La verificación está en cola. data lleva solo status, uid, asset, network, address y timestamp. Consulta como se describe en Pending y recheck. |
failed, error | Estados terminales del contrato. Un recheck posterior nunca los resuelve, así que deja de consultar. Leeri informa de una verificación que terminó sin veredicto mediante result: false, descrito más abajo. |
Los fallos responden HTTP 200
Un fallo no es un error HTTP. El endpoint responde 200 y pone result en false:
{
"result": false,
"balance": 0,
"description": "AML check failed\nInvalid token"
}
description tiene dos líneas. La primera es el texto fijo AML check failed y el motivo está en la segunda línea, que es donde tu cliente ya lo lee.
| Motivo en la segunda línea | Significado | Qué hacer |
|---|---|---|
Invalid token | El accessId es desconocido, está revocado o caducado, el token no coincide, o un recheck nombra un uid que nunca se aceptó para esta credencial. Todos estos casos responden igual. | Revisa la credencial y el cálculo del token. En un recheck, comprueba que reenvías el token original. |
Out of balance | Tu cuota y tu saldo prepagado están agotados. | Recarga y reintenta. Reintentar antes no ayuda. |
Analysis unavailable, please retry | Una fuente de datos de la que depende el veredicto no estaba disponible. No se cobra nada. | Reintenta más tarde. |
Service temporarily unavailable, please retry | Un fallo inesperado de nuestro lado. | Reintenta con retroceso exponencial. |
| Un mensaje de validación | El activo no es admitido, la dirección o el hash de transacción tienen una forma incorrecta para el activo, direction no es una dirección de flujo, o falta un campo o es desconocido. | Corrige la solicitud. Reintentar sin cambios devuelve la misma respuesta. |
| Un mensaje de capacidad | La capacidad para consultas está llena. No se acepta ni se cobra nada. | Reintenta tras una breve pausa. |
La única respuesta distinta de 200 en este endpoint es la limitación de solicitudes: está tras los mismos limitadores generales que la API nativa y responde 429 con Retry-After. Consulta Límites de solicitudes y cuotas.
Saldo
balance es el número de verificaciones que Leeri aún te atenderá: la cuota restante de tu plan más lo que el saldo prepagado todavía puede comprar. No es el saldo prepagado literal, que marca 0 durante la mayor parte de un periodo de facturación sano porque la cuota se gasta primero. El saldo prepagado exacto está en tu factura y en tu panel. Tras un fallo de autenticación, balance es 0.
Puntuación de riesgo y señales
riskscorees una fracción de 0 a 1, escalada a partir de la puntuación de 0 a 100 de Leeri. Nunca es un número entero sobre 100.signalscontiene el vocabulario fijo de 33 claves mostrado arriba. Cada clave está presente en cada respuesta y la mayoría vale0. Cada valor es una fracción de exposición. Los hallazgos y la exposición de Leeri se agrupan en estas claves por compatibilidad de protocolo; es una agrupación y no una correspondencia uno a uno de categorías.- Cuando la propia dirección verificada tiene una designación que explica la mayor parte de la puntuación, por ejemplo la dirección de una entidad sancionada,
signalsnombra esa categoría con1y todas las demás claves con0.riskscoreno cambia. - Un aviso de seguridad de la wallet, que advierte al propietario de la dirección y no dice nada sobre su riesgo, nunca se refleja en
riskscoreni ensignals. blackListsConnectionsesfalse, no un array vacío, cuando no hay coincidencias. Comprueba específicamentefalse, ya que un array vacío es verdadero.timestampes una cadena con el formatoYYYY-MM-DD HH:MM:SS, nunca un número Unix.
El informe de evidencias
data.pdfReport es un enlace permanente al informe de evidencias de la verificación, incluido en el precio de la verificación. El PDF se genera al abrir el enlace por primera vez. El enlace es null en el raro caso de que no se pudiera construir el informe, y el resto de la respuesta no se ve afectado. Una verificación pending aún no tiene pdfReport. Cualquiera que tenga el enlace puede leer el informe, así que trátalo como tratas el informe.
Campos que quedan vacíos
reportedAddressBalance, addressDetailsData y memo son siempre null. counterparty es null salvo que un hallazgo nombre a un exchange como propietario de la dirección. Los saldos de la dirección verificada en el momento de la verificación están en el informe de evidencias.
Pending y recheck
Cuando una verificación no puede terminar dentro de la solicitud, la respuesta trae result: true, data.status: "pending" y un uid. Consulta con:
POST https://api.leeri.ai/compat/amlbot/recheck
La ruta no lleva barra final. Los campos del formulario son accessId, uid y el token original.
- curl
- Node.js
- Python
curl -s -X POST "$AMLBOT_URL/recheck" \
--data-urlencode "accessId=$AMLBOT_ID" \
--data-urlencode "uid=$UID" \
--data-urlencode "token=$TOKEN"
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let body = firstResponse;
for (let attempt = 1; body.result && body.data.status === 'pending' && attempt <= 10; attempt += 1) {
await sleep(30_000);
const response = await fetch(`${AMLBOT_URL}/recheck`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ accessId: AMLBOT_ID, uid: body.data.uid, token }),
});
body = await response.json();
}
import time
body = first_response
attempt = 0
while body["result"] and body["data"]["status"] == "pending" and attempt < 10:
attempt += 1
time.sleep(30)
body = requests.post(
f"{url}/recheck",
data={"accessId": access_id, "uid": body["data"]["uid"], "token": token},
timeout=30,
).json()
- La respuesta es el mismo sobre que la verificación original: todavía
pending, un resultado final oresult: false. - Un recheck lee la verificación ya aceptada. No inicia nada nuevo y no se cobra.
- El token debe coincidir con el presentado en la solicitud original. Un token distinto, o un
uidque esta credencial nunca aceptó, respondeInvalid token. - El bucle que ya ejecuta tu cliente consulta cada 30 segundos hasta 10 intentos, un presupuesto de 5 minutos. Leeri conserva la asociación entre el
uidy el token original durante 30 minutos tras aceptar la verificación, así que ese bucle cabe de sobra. Pasado ese plazo, un recheck respondeInvalid token.
Probar el bucle de pending
Con una credencial sandbox, una verificación de dirección para el activo BTC y la dirección 1PENDNGCHECKADDRESSFRSANDBXTEST99 siempre responde pending, así que puedes ejercitar todo el bucle desde el primer día. Consulta Sandbox y live. La misma dirección con una credencial live se completa con normalidad.
Facturación
Una verificación compat se factura con las mismas reglas que una nativa. Se paga primero con la cuota restante y después con el saldo prepagado. Repetir la verificación de la misma dirección en la misma cadena en un plazo de 7 días es gratis. Una verificación fallida, una solicitud rechazada y un veredicto parcial porque una fuente de datos estaba caída no se cobran. El informe de evidencias forma parte de la verificación. Los detalles están en Límites de solicitudes y cuotas. Los planes y precios están en la página de precios.
Cuando tengas tiempo: la API nativa
El endpoint compatible deja tu integración sin cambios. La API nativa es a lo que conviene pasar cuando quieres más de cada veredicto:
- Cada hallazgo enlaza la fuente en la que se apoya, y el veredicto queda fijado para poder reproducirlo después. Consulta Verificaciones de dirección y Rederive.
- Un webhook entrega cada verificación terminada, así que no hay nada que consultar.
Ambas superficies funcionan sobre el mismo motor y el mismo ruleset, y puedes usarlas a la vez con la misma cuenta. Preguntas sobre la migración: hello@leeri.ai.