The response
Field by field, what a scored body contains. Every 200 has this shape, whether the answer came from a list lookup or from the model.
The whole body
One live body, so the sections below have something to point at. It is a real response, copied from a real screen, and every 200 carries the same keys in the same places.
{
"address": "0x97b1043abd9e6fc31681635166d430a458d14f9c",
"verdict": "FLAGGED",
"risk_score": 100.0,
"decided_by": {
"rule": "ofac_list",
"source": "rule",
"reason": "This wallet is on the OFAC sanctions list. Dealing with it is a legal risk."
},
"summary": "This wallet is on the OFAC sanctions list. Dealing with it is a legal risk.",
"checks": [
{
"name": "Sanctions list",
"status": "hit",
"detail": "Match"
},
{
"name": "Known hack and scam wallets",
"status": "clear",
"detail": "No match"
},
{
"name": "Mixers",
"status": "clear",
"detail": "No contact"
},
{
"name": "Behaviour",
"status": "hit",
"detail": "4 of 6, above the 2 a typical wallet shows"
}
],
"behaviours": [
{
"key": "exchange_exit",
"label": "Sends most of its money straight into an exchange",
"clear_label": "Does not funnel its money into an exchange",
"present": true
},
{
"key": "fast_onward",
"label": "The wallets it pays move the money on again quickly",
"clear_label": "The wallets it pays hold on to the money",
"present": true
},
{
"key": "round_amounts",
"label": "Sends round, hand-picked amounts",
"clear_label": "Amounts look ordinary, not hand-picked",
"present": false
},
{
"key": "forwards_fast",
"label": "Forwards nearly everything within a day of receiving it",
"clear_label": "Holds what it receives rather than passing it straight on",
"present": true
},
{
"key": "keeps_nothing",
"label": "Keeps nothing back — sends out as much as it takes in",
"clear_label": "Keeps some of what it receives",
"present": true
},
{
"key": "burst_then_quiet",
"label": "Worked in a burst, then went quiet for months",
"clear_label": "Used steadily rather than in one burst",
"present": false
}
],
"show_behaviours": true,
"behaviour_note": "4 of 6. A typical wallet shows 2, and even wallets we know are sanctioned typically show 4.",
"why": [
{
"kind": "decided",
"reason": "This wallet is on the OFAC sanctions list. Dealing with it is a legal risk.",
"feature": "ofac_list",
"value": null,
"pushed": null,
"percentile": null
},
{
"kind": "model",
"reason": "The wallet has been in use for 6 years.",
"feature": "age_days",
"value": 1981.5,
"pushed": null,
"percentile": null
},
{
"kind": "model",
"reason": "It has moved $18.3M in and $18.4M out. Almost everything that came in went straight back out.",
"feature": "usd_in",
"value": 18322838.163848,
"pushed": null,
"percentile": null
},
{
"kind": "model",
"reason": "It has dealt with 15 different wallets and handled 4 different tokens.",
"feature": "n_counterparties",
"value": null,
"pushed": null,
"percentile": null
}
],
"caveats": [],
"confidence": 0.927,
"confidence_label": "HIGH",
"ofac_hard_match": true,
"data_quality": {
"capped": false,
"truncated": false,
"n_tx_total": 246,
"coverage_ratio": 1.0,
"payload_missing": false
},
"model": {
"bags": 200,
"name": "pu_bag_variant_a_plus500",
"note": "percentile is the model's own number, 0-100, against the wallets it was trained on; when a list or contact rule fires the risk_score is 100 regardless of it",
"variant": "a",
"trained_at": "2026-08-26T13:55:21Z",
"recall_guarantee": "100% of the 88 OFAC-listed positives sit at or above the REVIEW line in out-of-fold validation",
"percentile": 98.2
},
"scoring_version": "v2",
"screening_id": 45779,
"scored_at": "2026-09-17T11:24:01.860246Z",
"stale": false,
"served_from": "cache"
}The answer
| Field | Type | What it is |
|---|---|---|
address | string | The address you asked about, lowercased. |
verdict | string | CLEAR, REVIEW or FLAGGED. Derived from risk_score, never set beside it. |
risk_score | number | null | 0 to 100. Null means the address was not scored at all, which is not the same as scoring low. |
decided_by.rule | string | The one thing that set the score: ofac_list, hacker_list, ofac_contact, hacker_contact, mixer_contact, contract, exchange_custody, unchecked or model. |
decided_by.source | string | "rule" for a deterministic lookup, "model" when nothing above it fired. |
decided_by.reason | string | That decision written out as a sentence. It is repeated as the first entry of why. |
summary | string | One or two sentences for a human reader. |
ofac_hard_match | boolean | True only for a direct hit on the sanctions list. A lookup, not a prediction. |
confidence | number | 0 to 1, how much evidence stood behind the answer. Not the probability that the wallet is bad. |
confidence_label | string | HIGH, MED or LOW, the same number banded. |
risk_score can be null
A contract, or a trail that ends inside an exchange's own wallet, is an address this API refuses to score rather than one it scored low. The verdict comes back REVIEW and the number comes back null. Rendering that as a zero turns a refusal into a clean bill of health.
why
The reasoning, as an array. Every entry has a kind, and there are exactly two of them.
| Field | Type | What it is |
|---|---|---|
kind | string | "decided" for the line that set the verdict, "model" for something the model noticed. The first entry is always the decided one. |
reason | string | The sentence itself, written for a person. |
pushed | string | null | Reserved. Intended to carry "up" or "down", which way an observation moved the score, but the scorer does not yet set it: today it is null on every entry. Do not branch on it. |
feature | string | null | The machine name of the signal behind the sentence. |
value | number | null | What that signal measured for this wallet. |
percentile | number | null | Reserved. Intended to place that value against ordinary wallets, 0 to 100, but the scorer does not yet set it per reason: today it is null on every entry. |
Reading a verdict works through what to do with the two kinds, and why showing them as one flat list misleads a reader.
checks
Every check that was run and how it came back. The clean ones are the point: three passes beside one hit is what makes the hit credible.
| Field | Type | What it is |
|---|---|---|
name | string | The check, named for a reader rather than for a log. |
status | string | "clear" if it ran and found nothing, "hit" if it found something, "unchecked" if it could not run. |
detail | string | One sentence on what that status means here. |
unchecked is not clear
A check that could not run has told you nothing. Count it separately in any tally you show a user, and never fold it in with the passes.
behaviours
Named patterns, each measured against the wallets confirmed to be sanctioned. They describe how a wallet moves money; on their own they accuse it of nothing.
| Field | Type | What it is |
|---|---|---|
key | string | Stable machine name for the behaviour. |
label | string | How to say it about a wallet that shows it. |
clear_label | string | null | The same behaviour said from the other side, so a clean result can name what was looked for instead of showing an empty list. |
present | boolean | null | Null, never false, when the input that decides it was missing. A behaviour nobody could look at must not read as one the wallet lacks. |
show_behaviours | boolean | Whether this list belongs in front of a customer at all. Two or three of these is what an ordinary wallet looks like. |
behaviour_note | string | null | One sentence about how this wallet moves money, safe to show whether or not the list is. |
caveats
Limits on the answer: how old the sanctions snapshot was, where the data ran out, where a custodian's wallet made one customer indistinguishable from thousands of others.
Caveats are not evidence
A caveat says what this answer could not see. It is not a finding against the wallet, and stacking caveats into a risk display turns a data limit into an accusation.
The record
| Field | Type | What it is |
|---|---|---|
screening_id | number | null | Id of the permanent record. Fetch it later through /v1/screening/{screening_id}. |
scored_at | string | ISO 8601 timestamp of when this answer was produced. |
scoring_version | string | Which scoring contract produced it. |
model | object | Name and version of the model behind a model decision. |
data_quality | object | How much history stood behind the answer, so a thin record can be recognised as thin. |
Freshness
| Field | Type | What it is |
|---|---|---|
stale | boolean | True when the record is past its 24 hour life and a refresh is already running. The answer is the last known one. |
served_from | string | "cache" for a live record, "stale" for one being refreshed behind your request. |
A stale answer is a real answer that has aged, and it is returned rather than withheld because the last known verdict beats no verdict. If your workflow cannot accept one, read stale and wait for the refresh.