Free Crypto KYT API — Enrich Sandbox, Sanction Checks, and Wallet Pre-Send Code
Interactive console + docs live at intelapi.publicaml.org. Same payload, same JSON your backend gets. This post turns that API into shippable code.
-
Base URL:
https://intelapi.publicaml.org - Auth (public enrich path): none
-
Chains:
ETH,BTC,TRON,BSC
Core: POST /v1/enrich
curl -sS -X POST 'https://intelapi.publicaml.org/v1/enrich' \
-H 'Content-Type: application/json' \
-d '{
"addresses":[{"wallet_address":"0x67d40ee1a85bf4a4bb7ffae16de985e8427b6b45","chain":"ETH"}],
"include":["aml_score","category"],
"top_n":5
}' | jq '.entities[0] | {label, category, sanctioned, aml_score}'
Live shape for that OFAC example: sanctioned: true, aml_score: 100, label Chatex (OFAC Sanctioned).
Rule #1 from the docs: sanctioned: true overrides everything else — block even if category looks like cex.
Optional include[]: aml_score | category | counterparties | source_of_funds. Cheap probe:
curl -sS -X POST 'https://intelapi.publicaml.org/v1/enrich' \
-H 'Content-Type: application/json' \
-d '{"addresses":[{"wallet_address":"0x9696f59e4d72e237be84ffd425dcad154bf96976","chain":"ETH"}],"include":["category"]}'
JavaScript — decision helper for Send
const BASE = "https://intelapi.publicaml.org";
const BAD = new Set(["mixer","sanction","scam","phishing","romance","hack","darknet","ransomware","terrorism"]);
export async function enrichAddress(wallet_address, chain = "ETH", include) {
const res = await fetch(`${BASE}/v1/enrich`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
addresses: [{ wallet_address, chain }],
include: include ?? ["aml_score","category","counterparties","source_of_funds"],
top_n: 10,
}),
});
if (!res.ok) throw new Error(`enrich ${res.status}`);
const data = await res.json();
const entity = data.entities?.[0];
if (!entity) throw new Error(JSON.stringify(data.not_found ?? "empty"));
return entity;
}
export function decideSend(entity) {
if (entity.sanctioned === true) return { action: "block", reason: "sanctioned" };
const score = Number(entity.aml_score ?? 0);
if (score >= 60) return { action: "review", reason: "high", score }; // docs: high
if (score >= 25) return { action: "monitor", reason: "medium", score }; // docs: medium
const bad = (entity.counterparties || []).filter(cp => BAD.has(String(cp.category||"").toLowerCase()));
if (bad.length) return { action: "review", reason: "bad_cp", bad };
const sof = entity.source_of_funds || {};
if (sof.risk_summary === "high" || (sof.top_sources||[]).some(s => s.sanctioned)) {
return { action: "review", reason: "sof" };
}
return { action: "allow", score, label: entity.label, category: entity.category };
}
// before wagmi sendTransaction / ethers send
export async function assertSendable(to, chain = "ETH") {
const entity = await enrichAddress(to, chain);
const d = decideSend(entity);
if (d.action === "block") throw new Error(`KYT block: ${entity.label}`);
return { entity, decision: d };
}
Python — CLI for desks / bots
import json, sys, requests
BASE = "https://intelapi.publicaml.org"
BAD = {"mixer","sanction","scam","phishing","romance","hack","darknet","ransomware","terrorism"}
def enrich(wallet, chain="ETH"):
r = requests.post(f"{BASE}/v1/enrich", json={
"addresses":[{"wallet_address": wallet, "chain": chain}],
"include":["aml_score","category","counterparties","source_of_funds"],
"top_n": 10,
}, timeout=60)
r.raise_for_status()
data = r.json()
if not data.get("entities"):
raise SystemExit(data.get("not_found"))
return data["entities"][0]
def decide(e):
if e.get("sanctioned") is True: return {"action":"block","reason":"sanctioned"}
score = float(e.get("aml_score") or 0)
if score >= 60: return {"action":"review","score":score}
if score >= 25: return {"action":"monitor","score":score}
if any(str(cp.get("category") or "").lower() in BAD for cp in e.get("counterparties") or []):
return {"action":"review","reason":"bad_cp"}
return {"action":"allow","score":score,"label":e.get("label")}
if __name__ == "__main__":
e = enrich(sys.argv[1], sys.argv[2] if len(sys.argv)>2 else "ETH")
print(json.dumps({"decision": decide(e), "label": e.get("label"), "aml_score": e.get("aml_score"), "sanctioned": e.get("sanctioned")}, indent=2))
More endpoints worth wiring
# edges between two addresses
curl -sS -X POST 'https://intelapi.publicaml.org/v1/transactions' \
-H 'Content-Type: application/json' \
-d '{"from":"0x4d5e63f1981444a4923f94fea61596b53a057113","to":"0xdd3d3c05a672b8a1112e158cd2fc6f577c2b6e1f","chain":"BSC","limit":20}'
# targeted counterparty / category query
curl -sS -X POST 'https://intelapi.publicaml.org/v1/counterparties' \
-H 'Content-Type: application/json' \
-d '{"wallet_address":"0x9696f59e4d72e237be84ffd425dcad154bf96976","chain":"ETH","category":"mixer"}'
# dataset coverage
curl -sS 'https://intelapi.publicaml.org/stats' | jq '{clusters, counterparties}'
Also on the docs page: /v1/entities, /v1/cluster-mappings, /v1/address-transactions, /health.
Field cheat sheet
| Signal | Action |
|---|---|
sanctioned: true |
refuse |
aml_score >= 60 |
high → review |
aml_score 25..59 |
medium → monitor |
dirty counterparties[].category
|
investigate |
cluster_size > 1000 |
treat as entity via cluster_id
|
Open the sandbox, paste an address, hit Copy as curl, then mirror that JSON in your wallet preflight. Product site: publicaml.org.
Top comments (0)