---
name: zscreen-wallet-screening
description: Screen an EVM wallet address for sanctions and illicit-funds exposure using the zScreen API. Use when asked to check, screen, or assess the risk of a crypto wallet address, or to decide whether an address is safe to transact with.
license: Proprietary
---

# Screening a wallet with zScreen

zScreen takes an EVM address and returns one of three verdicts, with the checks
behind it and a permanent record.

Base URL: `https://zscreenapi.zeruai.org`
Auth: `Authorization: Bearer zsk_...` on every request.

A key is created by a person, at https://zscreen.zeruai.org/dashboard, after
signing in. You cannot mint one. If you have neither a key nor a way to pay,
say so and stop rather than guessing at a key.

A request without a key is allowed when it is paid: the API answers `402` with
x402 payment requirements, and an x402 client (`x402` on PyPI, `@x402/fetch` on
npm) signs the USDC transfer and retries the request on its own. A paid `202`
returns a `status_url` that carries a poll token: poll it exactly as given, no
key needed.

## The rule that matters most

**A response without a verdict is not a clean wallet.**

Only a `200` carrying a `verdict` field is an answer. Every other outcome means
no conclusion was reached, and none of them may be reported as safe, clear, or
low risk:

| Status | Meaning | May you call it clean? |
| --- | --- | --- |
| `200` + `verdict` | Scored. | Report the verdict. |
| `202` + `job_id` | Never seen before, being fetched. | **No.** Poll the job. |
| `422` | The chain has nothing to score. | **No.** A refusal. |
| `402` | Payment required: $0.50 USDC on Base via x402. Pay and retry, or stop. | **No.** Nothing was screened. |
| `429` | Rate limited. | **No.** Wait for `Retry-After`, retry. |
| `503` | Store briefly unreachable. | **No.** Retry. |

Branch on `200` **and** the presence of a verdict. Never branch on whether a
field happens to exist, and never infer safety from the absence of a warning.
If you cannot get a verdict, tell the user you could not screen the address.

## Screen an address

```bash
curl -X POST https://zscreenapi.zeruai.org/v2/screen \
  -H "Authorization: Bearer $ZSCREEN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address":"0xd08a234c5215a8122c836187b23f60046a90afd8"}'
```

A scored response:

```json
{
  "address": "0x...",
  "verdict": "FLAGGED",
  "risk_score": 100,
  "decided_by": { "rule": "ofac_contact", "source": "rule",
                  "reason": "This wallet dealt directly with an address on the OFAC sanctions list." },
  "summary": "...",
  "checks": [ { "name": "...", "status": "clear|hit|unchecked", "detail": "..." } ],
  "why": [ { "kind": "decided", "reason": "..." } ],
  "caveats": ["..."],
  "scored_at": "2026-09-04T...", "stale": false
}
```

## The three verdicts

- **CLEAR**: nothing connects this address to a sanctioned or illicit source.
- **REVIEW**: the trail reaches a custodian, where one wallet holds many
  customers and the funds stop being separable. The result names the boundary.
- **FLAGGED**: exposure to a sanctioned or illicit source, direct or through
  intermediaries.

`risk_score` is a rank from 0 to 100 against a reference population, not a
probability. `100` means a list or contact rule fired. It is `null` when the
address is declined rather than scored low, such as a contract. **A null score
is not a low score.** Read `decided_by` to see which case applies.

Quote `decided_by.reason` and `summary` when explaining a verdict. Do not invent
a reason, and do not soften a FLAGGED result.

## When the wallet is new (202)

```bash
curl https://zscreenapi.zeruai.org/v2/job/41 -H "Authorization: Bearer $ZSCREEN_KEY"
```

Poll every one to two seconds. `state` goes `queued` then `running` then `done`;
only `done` carries a `result`. A `failed` job is not a CLEAR: the fetch did not
complete and the wallet is still unscored. Polling is free and never counts
against the quota.

## Limits

Each key has a requests-per-second limit, 10 by default. Over it is `429` with
`Retry-After`; back off rather than resending immediately.

An account gets **100 screens for its lifetime**. A `200` or a `202` each count
once. Polling a job, and a `422`, cost nothing. The screen after the limit is
`402`, which is not a verdict: it carries x402 payment requirements, and a paid
retry is charged only when the answer is a `200` or a `202`.

Do not screen the same address repeatedly. Results are cached server-side and a
repeat call still spends the allowance.

## Other endpoints

- `GET /v2/screen/{address}`: same as the POST, address in the path.
- `POST /v1/screen/batch`: up to 100 addresses in one call, key only. No v2 yet.
- `GET /v1/screening/{id}`: one stored record, scoped to your key.
- `GET /v1/wallet/{address}/screenings`: this key's history for an address.
- `GET /healthz`: liveness, no key needed.

## MCP

The same screening is available as a hosted MCP server at
`https://zscreenapi.zeruai.org/mcp` (Streamable HTTP, same bearer key). Its
tools are `screen_wallet`, `get_job`, `get_screening` and
`list_wallet_screenings`. Every result carries an `assessed` field, and it
follows the same rule as above: `assessed: false` is never a clean wallet.

Prefer MCP when the client supports it. Fall back to the HTTP API otherwise.

## Reporting to a person

Give the verdict, the reason from `decided_by`, and the caveats. If the address
was queued, refused, or rate limited, say plainly that it was **not** screened
rather than implying anything about its safety. Full docs:
https://zscreen.zeruai.org/docs
