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.

200 OK
{
  "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

FieldTypeWhat it is
addressstringThe address you asked about, lowercased.
verdictstringCLEAR, REVIEW or FLAGGED. Derived from risk_score, never set beside it.
risk_scorenumber | null0 to 100. Null means the address was not scored at all, which is not the same as scoring low.
decided_by.rulestringThe one thing that set the score: ofac_list, hacker_list, ofac_contact, hacker_contact, mixer_contact, contract, exchange_custody, unchecked or model.
decided_by.sourcestring"rule" for a deterministic lookup, "model" when nothing above it fired.
decided_by.reasonstringThat decision written out as a sentence. It is repeated as the first entry of why.
summarystringOne or two sentences for a human reader.
ofac_hard_matchbooleanTrue only for a direct hit on the sanctions list. A lookup, not a prediction.
confidencenumber0 to 1, how much evidence stood behind the answer. Not the probability that the wallet is bad.
confidence_labelstringHIGH, 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.

FieldTypeWhat it is
kindstring"decided" for the line that set the verdict, "model" for something the model noticed. The first entry is always the decided one.
reasonstringThe sentence itself, written for a person.
pushedstring | nullReserved. 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.
featurestring | nullThe machine name of the signal behind the sentence.
valuenumber | nullWhat that signal measured for this wallet.
percentilenumber | nullReserved. 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.

FieldTypeWhat it is
namestringThe check, named for a reader rather than for a log.
statusstring"clear" if it ran and found nothing, "hit" if it found something, "unchecked" if it could not run.
detailstringOne 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.

FieldTypeWhat it is
keystringStable machine name for the behaviour.
labelstringHow to say it about a wallet that shows it.
clear_labelstring | nullThe same behaviour said from the other side, so a clean result can name what was looked for instead of showing an empty list.
presentboolean | nullNull, 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_behavioursbooleanWhether this list belongs in front of a customer at all. Two or three of these is what an ordinary wallet looks like.
behaviour_notestring | nullOne 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

FieldTypeWhat it is
screening_idnumber | nullId of the permanent record. Fetch it later through /v1/screening/{screening_id}.
scored_atstringISO 8601 timestamp of when this answer was produced.
scoring_versionstringWhich scoring contract produced it.
modelobjectName and version of the model behind a model decision.
data_qualityobjectHow much history stood behind the answer, so a thin record can be recognised as thin.

Freshness

FieldTypeWhat it is
stalebooleanTrue when the record is past its 24 hour life and a refresh is already running. The answer is the last known one.
served_fromstring"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.