Quickstart
A verdict in your terminal in four steps. Everything else is on the pages after this one.
1. Get a key
Mint one from your dashboard. It starts with zsk_ and is shown once.
export ZSCREEN_KEY=zsk_...2. Screen an address
Paste this. It runs as written.
curl -s https://zscreenapi.zeruai.org/v2/screen \
-H "Authorization: Bearer $ZSCREEN_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"0xd08a234c5215a8122c836187b23f60046a90afd8"}'GET /v2/screen/{address} does the same thing without a body.
3. Read the answer
A 200 looks like this. Trimmed to what you act on.
{
"address": "0xd08a234c5215a8122c836187b23f60046a90afd8",
"verdict": "REVIEW",
"risk_score": 87.4,
"summary": "Needs a human look. Not on any list, and no contact with a listed address or a mixer. The model scored it 87.4 out of 100.",
"decided_by": {
"rule": "model",
"source": "model",
"reason": "Not on any list, and no contact with a listed address or a mixer. The model scored it 87.4 out of 100."
},
"checks": [
{ "name": "Sanctions list", "status": "clear", "detail": "No match" },
{ "name": "Known hack and scam wallets", "status": "clear", "detail": "No match" },
{ "name": "Mixers", "status": "clear", "detail": "No contact" },
{ "name": "Behaviour", "status": "hit", "detail": "Unusual overall" }
],
"why": [
{ "kind": "decided", "reason": "Not on any list, and no contact with a listed address or a mixer. The model scored it 87.4 out of 100." },
{ "kind": "model", "reason": "Money moved straight back out within a day.", "pushed": "up" }
],
"confidence_label": "HIGH",
"screening_id": 418822,
"scored_at": "2026-09-16T09:14:22Z",
"served_from": "cache"
}Four fields carry the decision:
verdictisCLEAR,REVIEWorFLAGGED. Branch on this.risk_scoreis 0 to 100 and always agrees with the verdict. A list or contact hit is 100.checksis every check that ran, including the ones that came back clean. Show it to an analyst.whystarts with what decided the verdict. The rest is what stood out.
The full body carries more: measured behaviours, caveats, confidence and the model record. The response lists every field.
4. Handle a 202
A wallet we have never seen has to be fetched from the chain first. You get a 202 and a job.
{
"address": "0x1f39eb3896756172728a2cfb116a6ec37d807125",
"status": "queued",
"job_id": 90114,
"status_url": "/v2/job/90114",
"jobs_ahead": 3,
"detail": "This wallet has no history on file, so it is being fetched from the chain. Poll status_url. A wallet is NOT low risk because it has not been assessed yet."
}Poll the job until state is done.
curl -s https://zscreenapi.zeruai.org/v2/job/90114 \
-H "Authorization: Bearer $ZSCREEN_KEY"{
"job_id": 90114,
"address": "0x1f39eb3896756172728a2cfb116a6ec37d807125",
"state": "done",
"result": { "verdict": "CLEAR", "risk_score": 4.1, "...": "the same body as above" }
}Most addresses come back in under a minute. jobs_ahead tells you how busy the queue is.
The one rule
No verdict is not a clean wallet
A 202, a 402, a 503 and every other non-answer carry verdict: null or no verdict field at all. Treating any of them as CLEAR is the one mistake this API is built to make impossible. Branch on the presence of verdict, never on the status code alone.
Next
- Endpoints for batch screening, the permanent record, and every parameter.
- The response for every field in the body.
- The full report for the long answer, field by field.
- Reading a verdict for what the three words mean and what to do with each.
- Limits and errors for quota, rate limits and every status code.
- MCP server to give the same screening to an assistant.