---
title: "Screen one or more addresses: who they are and what they are worth in risk"
method: POST
path: "/v2/enrich"
tags: ["Screening"]
---

# Screen one or more addresses: who they are and what they are worth in risk

`POST /v2/enrich`

## Request

- Base URL: `https://intelapi.publicaml.org`
- URL: `https://intelapi.publicaml.org/v2/enrich`
- Auth: one of:
  - API key in header `X-API-Key`
  - none

The primary endpoint. Give it addresses, get back for each one what is claimed about it, one winner per question (`axes`), its display name (`label`), its money-laundering risk (`risk`), and on request its counterparties, where its money came from and the incidents it appears in.

Batch: up to 100 addresses in one call, each with its own chain, enriched concurrently. Each address costs one rate-limit unit.

**Read `not_found` before concluding anything.** Addresses we hold no data for are NOT in `entities`; they are listed in `not_found` with a reason. Treating a missing address as a zero score is the commonest way to misread this endpoint: it turns 'we have never seen this wallet' into 'this wallet is clean', which is exactly backwards for a freshly created one.

**A missing block means it was not asked for.** `include` selects blocks and the response carries exactly those: no `risk` key means the score was not requested, never that it is zero.

**Who the address is lives in `axes`, not in one field.** Seven questions (`account_type`, `business`, `actor`, `membership`, `legal_status`, `asset_control`, `structure`), always all seven keys, `null` where nobody made a claim. Claims on different axes do not compete, so a sanctioned exchange is reported as both.

**The platform's risk is the address's risk.** When an address resolves to a platform (`axes.business.brand`) — by its own label or through its cluster — the platform's score, a sanction on the organisation or the risk of its jurisdiction, is part of the address's direct exposure (`risk.breakdown.brand_direct`), capped by the rank of whoever linked the address to the platform. An exchange hot wallet scores 0 on its traffic and 50 to 90 if the exchange itself is designated, depending on who labelled it.

All time references are block numbers, not timestamps.

## Request body

- object
  - `addresses` object[], required: The addresses to screen, at most 100. Each needs its own chain. More than 100 is a 400 and costs nothing.
    - `wallet_address` string, required: The address to screen. NOTE the field name: this endpoint uses `wallet_address`, while the trace and transaction endpoints use `address`. Sending `address` here is a 400 that says so. The address to look up, in its normal display form. * bitcoin — base58 (`1…`, `3…`) or bech32/bech32m (`bc1…`, `bc1p…`). Case is SIGNIFICANT for base58; do not lowercase it. A 64-character lowercase TRANSACTION HASH is also accepted here: on bitcoin the graph is bipartite (address → transaction → address), so a transaction is a node with its own neighbours and its own findings. CoinJoin is classified on the transaction, not on the participants, and this is how you read it. * ethereum, bsc — `0x` + 40 hex characters, any case. * tron — base58 (`T…`, 34 characters); the `0x` 20-byte form is accepted too and is what the API stores and echoes. * solana (enrich only) — base58 account address, 32-44 characters (no `0`, `O`, `I`, `l`), decoding to exactly 32 bytes. Case is SIGNIFICANT: send it exactly as the explorer prints it; a lowercased Solana address is a different (usually non-existent) account.
    - `chain` 'bitcoin' | 'ethereum' | 'bsc' | 'tron' | 'solana', required: Which blockchain the address belongs to. Send one of the values in the enum. The short forms used by the older clients (ETH, BTC, TRX, BNB) and any capitalisation are still accepted, but responses always echo the canonical spelling. Getting this wrong is the single commonest integration bug: the same 0x-prefixed address exists on ethereum, bsc and tron and means a different wallet on each, so the chain is part of the identity, never a hint. If it does not match the address format the call fails with `invalid_address_format`, which names the chain the address does look like. `solana` (short form `SOL`) is LABEL-ONLY: we hold attribution for Solana accounts (who the address is, and who says so) but no transaction graph, risk seeds or propagation. A Solana entity therefore carries `label`, `label_basis` and `axes`, and its `risk` block says `scored: false` with `unscorable_reason: chain_label_only` — no score, which is NOT the same as a clean score. `counterparties`, `source_of_funds` and `incidents` are never returned for it. Solana is accepted by the enrich endpoints only; every other endpoint answers `unsupported_chain`.
  - `include` string[]: Which blocks to compute and return. The names are the blocks of the response: `axes` returns `label` + `axes` (+ `kyc_origin`), `risk` returns `risk`, `counterparties` returns `counterparties` and the fields derived from them (`counterparties_meta`, `counterparties_truncated`, `behavioral_signals`, `inferred`, `mixer_interaction`, `peel_chain`), `source_of_funds` returns `source_of_funds`, `incidents` returns `incidents` + `incidents_total`. `category` and `aml_score` are accepted as older names for `axes` and `risk`. Default (no `include`): `axes`, `risk`, `counterparties`, `source_of_funds`. `incidents` is an add-on: off by default, and asking for it alone ADDS it to the defaults rather than replacing them. To trim the defaults, name the blocks you want. An unknown value is a 400, never a silently dropped block. This is the cost knob. `counterparties` and `source_of_funds` scan the graph and dominate the call; for bulk labelling `include: ["axes"]` is roughly an order of magnitude faster and far less likely to meet a degraded risk store.
  - `top_n` integer: How many counterparties (and source-of-funds buckets) to return per address.
  - `debug` boolean: Adds `_trace`, a server-side timing report. Diagnostic; its shape is not part of the contract.

## Response `200`

Per-address results. Check both `entities` and `not_found`.

- object
  - `entities` EnrichEntity[], required: One per address we hold data for. May be shorter than the request.
    - `wallet_address` string, required: The address as you sent it. Pair results with your request by this field.
    - `chain` 'BTC' | 'ETH' | 'BSC' | 'TRON' | 'SOL', required: Echoed as the short chain code, whichever spelling was sent.
    - `first_block` integer, nullable: First block the address was seen in, when known. A block number, not a timestamp.
    - `last_block` integer, nullable: Last block the address was seen in, when known.
    - `label` string, nullable: Display name, e.g. 'Binance' or 'unnamed hub 0x71191c61…'. A name, not a class: read `axes` for what the address is and who says so. Block `axes`. When several sources name the address, the name is taken from the more trusted one. The address's own risk finding can take the name only when it names the address: its category says what the address is (`hack`, `mixer`, `sanction`, ...), or the address is recorded as the perpetrator of the incident behind the finding. Such a finding replaces a name produced by our own graph-shape heuristics (the `structure` axis, e.g. `sweep-intermediary`), a name that is only a taxonomy tag, or a name from a feed ranked lower than the finding's source. A finding whose title only describes an event, or whose stored name is a taxonomy tag (`sanction`, `issuer-frozen`), never replaces a name. Only the name follows this rule; `axes` and `risk` do not change with it.
    - `axes` Axes: What is claimed about the address, one winner per QUESTION. Always all seven keys; a key whose value is `null` means nobody made a claim on that question, which is not the same as 'known to be nothing'. Claims on different axes never compete: an address can be an exchange (`business`), sanctioned (`legal_status`), frozen by an issuer (`asset_control`) and a member of a hub (`membership`) all at once, and every one of those is reported. Only claims on the SAME axis compete, and the source with the higher `source_rank` wins. An axis is an attribution, not a risk figure: the price of the address is `risk`. Key order in the object carries no meaning.
      - `account_type` object, nullable, required: What the address is technically: contract, proxy, token, multisig, wallet, burn, … null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: Cluster that supplied an inherited attribution; also identifies membership groups.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
      - `business` object, nullable, required: What the address does: cex, dex, defi, bridge, mixer, gambling, deposit, … null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: Cluster that supplied an inherited attribution; also identifies membership groups.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
        - `role` 'hot_wallet' | 'cold_wallet' | 'deposit' | 'gas_supplier' | 'treasury' | 'custody': Which address of the business this is. Refines `category`, never replaces it.
        - `role_source` string, nullable: Who said so about the role.
        - `brand` object: The platform (brand) this address belongs to, resolved from its own label or, failing that, from its cluster's binding (`basis`). Refines `category` like `role` does — 'whose exchange' is the same question as 'is it an exchange' — so it is not an eighth axis. Present only when the address resolves to a brand. When nobody made a `business` claim the axis is still filled: `{category: null, basis: "brand", brand}`. Its `risk_score` is part of the address's own risk: see `risk.breakdown.brand_direct`. The full record (licences, KYC, every ground) is at GET /v1/brands/{name_key}, linked in `detail`.
          - `entity_id` integer
          - `name_key` string: The key to ask /v1/brands for.
          - `display_name` string, nullable
          - `risk_score` integer, nullable: 0..100, worst component wins; null = no component at all (no country and no designation), which is NOT 0. The threshold for 'high risk' and the full rule are in brand_risk_policy.
          - `reason` object, nullable: The WORST component — the one the score came from, and the one field that says whether it was a sanction or a jurisdiction: {kind: designation, count, status, score} or {kind: jurisdiction, code, list, status, valid_from, score}. null when there is no component.
          - `designated` boolean: The ORGANISATION itself carries an active sanction. It reaches the address score through `risk_score` (breakdown `brand_direct`), not through this flag.
          - `jurisdiction` object
            - `domicile_country` string, nullable: Country of REGISTRATION.
            - `management_country` string, nullable: Country of ACTUAL CONTROL — a different fact, filled only on a regulator's or court's finding.
            - `licence_countries` string[]: Issuing countries of the licences and registrations valid on as_of.
            - `unchecked` string[]: Countries of this venue that have NO row for as_of in the FATF or EU list. Empty means everything the venue has was checked; a non-empty list means risk_score is a lower bound and names who could raise it. This replaces a bare completeness flag — the flag itself is in the full record.
          - `as_of` string, date: The date the verdict was computed for.
          - `basis` 'label' | 'cluster' | 'null', nullable: What resolved the brand: `label` — the address's own label, `cluster` — the binding of its cluster.
          - `detail` string: Path of this brand's full record: /v1/brands/{name_key}.
      - `actor` object, nullable, required: Who is behind it: individual, fund, dao, government, state-actor, scammer, hacker, … null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: Cluster that supplied an inherited attribution; also identifies membership groups.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
      - `membership` object, nullable, required: Which group the address belongs to. `group-entity`: a named owner. `group-cospend`: one owner proven by co-spending (bitcoin), identity unknown. `group-hub`: addresses that pay into one convergence point, owner unknown. `group-community`: addresses that transact with each other often; proves NOTHING about ownership. `group-cex-origin`: funded from the same exchange withdrawals. Do not put one name on a whole group unless it is `group-entity`. null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: The cluster id, e.g. `entity:eth:binance` or `unnamed:eth:0x…`. Pass it to POST /v1/entities or /v1/cluster-mappings.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
        - `members` integer, nullable: How many addresses the group holds.
        - `is_anchor` boolean: true when this address is the group's own convergence point rather than a member.
      - `legal_status` object, nullable, required: Legal status: sanction, terrorism, ransomware. The designation that proves it travels inside the claim. null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: Cluster that supplied an inherited attribution; also identifies membership groups.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
        - `designation` object: The listing itself: which authority, which list, under which name, and when we last verified it.
          - `authority` string: e.g. OFAC, OFSI.
          - `list` string: e.g. SDN, UK Sanctions List.
          - `jurisdiction` string
          - `entity_name` string: The name the list designates.
          - `as_of` string: Date of the list snapshot the listing was verified against.
          - `feed` string
          - `reference_url` string
          - `verified_against` string: The feed the listing was checked in.
          - `also_listed_by` object[]: Other authorities that list the same address, same fields.
        - `jurisdictions` string[]: US, EU, GB, UN. The union over every listing, not only the winning one.
      - `asset_control` object, nullable, required: Whether an issuer has technically blocked the funds (`issuer-freeze`). A separate question from `legal_status`: a sanctioned address can move USDT freely, and a frozen one cannot move it whatever its legal status. null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: Cluster that supplied an inherited attribution; also identifies membership groups.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
        - `issuers` string[]: EVERY issuer that froze this address (e.g. circle, tether), not only the one named in `source`.
        - `assets` object[]: One row per token and issuer, with the freezing transaction.
          - `token` string
          - `issuer` string
          - `state` string: `frozen`, or the state it went to after a release.
          - `frozen_at` string, date-time, nullable
          - `freeze_block` integer, nullable
          - `freeze_tx` string, nullable
      - `structure` object, nullable, required: The address's position in the shape of the graph, computed by us: hub, consolidation, sweep-intermediary, coinjoin. A POSITION, never an identity: an exchange deposit hub and a wallet drainer collecting from victims produce the same shape. Read `business` and `actor` for who it is. null = no claim on this axis.
        - `category` string: The winning category on this axis, lower case, from GET /v1/categories (e.g. `cex`, `sanction`, `group-hub`).
        - `source` string, nullable: The feed or pass that made the claim, named as stored, e.g. `ofac:sdn_advanced`, `entities-discoverer`, `publicaml:clustering`. null means the stored claim never carried a source, not that one is hidden.
        - `source_rank` integer, nullable: How far we trust that source, 0-100. It is what decided the winner when several sources answered the SAME axis; it is never compared across axes.
        - `source_confidence` number, nullable: Our trust in the source on a 0..1 scale. Absent when the source has no trust row.
        - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not comparable with `source_confidence`, which is ours about the source.
        - `applies_to` 'cluster': Present for an external attribution inherited from a cluster.
        - `group_id` string: Cluster that supplied an inherited attribution; also identifies membership groups.
        - `basis` 'attribution' | 'derived' | 'brand': `attribution`: the source stated this category and answers for it. `derived`: we read it off the source's own wording (e.g. `deposit` from a label 'Binance Deposit'); the source never said it. `brand` (`business` axis only): nobody said what KIND of business this is, but the address resolves to a platform, so the axis carries `brand` with `category: null`.
        - `method` string: Which rule produced it, e.g. p1-hub-discovery.
        - `anchor` string: The convergence point the shape is keyed on.
    - `label_basis` object: Block `axes`, label-only chains (`SOL`) only. Who named the address and in which words. On Solana the attribution rows carry a name but no category on purpose, so `axes` is usually all `null` and this block is where the name is signed.
      - `kind` 'attribution'
      - `source` string: The feed that named the address, e.g. `pumpfun:official-docs`, `orbitflare:onchain`.
      - `source_confidence` number, nullable: Our trust in `source` on a 0..1 scale (its rank / 100).
      - `stated_as` string, nullable: The name as the source gave it.
      - `labels` string[]: The source's own tags for the address, verbatim.
      - `confidence` number, nullable: The source's own confidence in the row.
      - `as_of` string, date, nullable: When the source last stated it.
      - `reference` string, nullable: A link to the document behind the claim, when the source gave one.
    - `label_only` true: Present (true) only for a chain served from attribution alone (`SOL`): no graph, no score, no counterparties.
    - `risk` Risk: The price of the address. Present whenever the score was requested (`risk`, or `aml_score`, in `include`, or no `include` at all). ABSENT when it was not requested: absence means 'not asked', never 'no risk'.
      - `score` number, nullable: Money-laundering risk, 0 (nothing known against it) to 100. A RISK figure, not a share of funds: an address that received 1% of its money from a sanctioned entity can score high, and the number never says how much of the balance is tainted. It combines its parts by MAX, not by sum, so the terms in `breakdown` do not add up to it and should not be presented as if they do. null when `scored` is false: withheld, NOT zero and NOT clean. And 0 for an address in `entities` is a different statement from an address listed in `not_found`, which we hold no data for.
      - `level` 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' | 'null', nullable: The coarse band `score` falls in, for callers that branch rather than plot: * `LOW` — score below 25 * `MEDIUM` — 25 to 59 * `HIGH` — 60 to 89 * `CRITICAL` — 90 or above, OR the address is sanctioned, at any score The sanctions override is deliberate: a designated address whose score was floored must still reach a consumer that only rejects the top band. The reason is in `axes.legal_status` and `breakdown.sanction_direct`.
      - `scored` boolean: false = no score was produced rather than one computed without its inputs; `score`, `level` and `breakdown` are then null and `unscorable_reason` says why: the risk store did not answer (transient), or the chain is label-only (Solana, permanent). `axes` and `label` are read separately and are complete. Branch on this, not on the number.
      - `breakdown` RiskBreakdown: Why the score is what it is (`risk.score` on /v2/enrich). Combine by MAX, not by sum. Most sub-blocks appear only when they apply, so absence means 'did not fire', never 0.
        - `direct_exposure` number: Risk from this address's OWN labels and seeds.
        - `indirect_exposure` number: MAX over the propagated origins — not their sum.
        - `behavioral_risk` number, nullable: null = no behavioural signal assessed; 0 = assessed and contributed nothing.
        - `exposure_direction` 'inbound' | 'outbound' | 'mixed'
        - `sanction_direct` object: Present when the address is itself on a sanctions list. `score` is capped by the rank of `source`, like every claim: a listing by the authority itself (rank 100) keeps 100, a vendor's mirror of it (`arkham:sanctioned`, 90) gives 90, a sanction known only from a feed's label text (rank 50) gives 50. `uncapped_score` carries the severity before the cap.
          - `label` string
          - `score` number: Severity capped by the rank of `source`.
          - `uncapped_score` number: The severity before the cap. Present only when the cap lowered it.
          - `source` string
          - `source_confidence` number, nullable
          - `designation` object: The listing that proves it; same object as `axes.legal_status.designation`.
          - `jurisdictions` string[]: US, EU, GB, UN. Absent means the authority was never recorded, not that there is none.
        - `brand_direct` object: The address belongs to a platform (`axes.business.brand`) whose own risk score is above 0, and that score is part of this address's DIRECT exposure: an active designation of the organisation is 100, a lifted one 50, and the platform's jurisdiction counts in full (FATF black list 100, EU list 50, FATF grey list 40, weak-supervision licensing jurisdiction 20), worst component wins. So every address of a casino licensed only in Curaçao or Anjouan carries at least 20. CAPPED BY THE RANK OF THE LINK, like any claim (`min(severity, rank)`): the platform's score says how bad the PLATFORM is, and how sure we are that this address is the platform's is said by whoever linked them. Through the address's own label it is the best rank among the label sources that lead to the platform (an Arkham entity label 50, Arkham's sanctions list 90); through the cluster it is the rank of our clustering (50). When the cap bites, `uncapped_score` and `link_rank` say so. A designated exchange's hot wallet therefore scores 50 to 90 through this term, not 100. Combined with the other direct terms by MAX, not by sum: an address sanctioned itself AND belonging to a sanctioned platform is not the sum of the two. The entity floor never removes it — the floor only clears indirect exposure. Absent when the address has no platform, the platform's score is null (no data) or 0, or the cap leaves 0.
          - `score` number: What this term contributes: the platform's `risk_score` capped by `link_rank`, 0..100.
          - `uncapped_score` number: The platform's own `risk_score`. Present only when the cap lowered it.
          - `link_rank` integer: Rank of the source that linked this address to the platform (its label source, or our clustering for `basis: cluster`). Present only when it lowered the score.
          - `name_key` string: The key to ask /v1/brands for.
          - `basis` 'label' | 'cluster': `label`: the address's own label names the platform. `cluster`: its cluster is bound to the platform.
          - `reason` object, nullable: The worst component, same object as `axes.business.brand.reason`: {kind: designation, count, status, score} or {kind: jurisdiction, code, list, status, valid_from, score}.
          - `detail` string: Path of the platform's full record: /v1/brands/{name_key}.
        - `identity_direct` object: The address IS a mixer (attributed as one) and scores for that identity, not only for its exposure.
          - `category` string
          - `score` number
        - `outbound_direct` object: This address itself paid a designated counterparty, priced by that category at one hop. See `outbound_exposure` for the payments.
          - `category` string
          - `score` number
          - `source` string, nullable
          - `source_confidence` number, nullable
          - `reason` string
        - `frozen_direct` object: Token issuer (Tether/Circle) has frozen this address.
          - `issuers` string[]
          - `score` number, nullable: null when the freeze is recorded but not priced on its own.
          - `scored_as` string
          - `tokens` string[]
        - `entity_floor` object: The score was forced to a known-clean floor because the address is identified infrastructure (an exchange hot wallet, a token contract). `raw_score` is what it would have been. Read this before reporting a 0.
          - `reason` string
          - `raw_score` number
        - `incident_bonus` number: Flat surcharge added because the incident canon names this address as a PARTICIPANT in a recorded incident — `incidents[].implicates_address` is true, i.e. role `perpetrator` or `intermediary`. Flat per address, not per incident: five reports about one address are still one claim. Victims, `recipient` and `mentioned` addresses never get it, and neither does anything carrying `entity_floor` or `non_wallet` — a community report naming the USDT contract describes a token's user base, not a wallet. Added on top of the other terms rather than combined by MAX, and capped with them at 100. Absent means it did not apply.
        - `non_wallet` object: Not a wallet at all: a mint/burn pseudo-address or a token contract. Both exposures forced to 0.
          - `reason` string
          - `suppressed_direct` number
          - `suppressed_indirect` number
          - `registry` string
        - `contacts` object: Real counterparties whose transferred value is below the materiality threshold: recorded on purpose, scored at zero on purpose. A dusting attack must not raise a victim's score.
          - `count` integer
          - `max_value_usd` number
          - `threshold_usd` number
          - `reason` string
        - `sanctions_contact` object: Sub-threshold contact with a designated address. Lifted out of `contacts` because it is reportable regardless of amount.
          - `count` integer
          - `max_value_usd` number
          - `reason` string
        - `propagated_sources` object[]: At most 10 rows, ordered by (hops asc, score desc).
          - `src` string: The origin the risk started at.
          - `src_label` string, nullable: Display name of the origin: its own label, else the title of the incident in which it is the recorded perpetrator, else null. Never the risk class — that is `category`.
          - `category` string, nullable: sanction, mixer, hack, phishing, darknet, …
          - `source` string, nullable: Who made the accusation, e.g. 'ofac:sdn_advanced'.
          - `source_rank` integer, nullable: How far we trust the source that made this accusation, 0-100. The score you see is `min(severity, source_rank)`, and the rank also bounds how far the claim travels: `ceil(min(base_hops, 8) * source_rank / 100)` hops. Replaced the old `source_tier` on 2026-09-22; feed tiers no longer exist.
          - `source_kind` string, nullable: What kind of claimant this is: `feed` (an outside provider), `detector` (our own heuristic over graph shape) or `investigation` (our own case work).
          - `severity` number, nullable: What the ACCUSATION weighs on its own, before the rank bounds it. Keyed by category, not by source: a `hack` weighs the same whether it came from a court filing or a web form.
          - `hops` integer: How many TRANSFERS separate `src` from this address.
          - `hops_economic` integer, nullable: The distance the policy CHARGED, which is what the score is taken from. It differs from `hops` for two independent reasons: on bitcoin the graph runs address → transaction → address, so one economic step is two hops; and a step through an address that passed the money straight on (one payer, one payee, almost nothing kept) is not charged at all — those steps are counted in `collapsed_hops`.
          - `collapsed_hops` integer: How many steps on the path were taken but NOT charged, because each went through an address that merely passed the money on. 0 for every ordinary finding. A high value next to a near-origin score is the honest shape of a layering chain: the money moved many times without changing hands.
          - `source_confidence` number, nullable: Our trust in `source` on a 0..1 scale.
          - `reference` string, nullable: A link or key for the claim at the origin, when the source gave one.
          - `score` number: Hop-decayed RISK contributed by this origin. Never a share of funds.
          - `value_usd` number, nullable: How much money could have travelled this path, in USD: the value of the NARROWEST hop between `src` and this address, priced on the day that hop happened. It is a CEILING, not the amount received — a chain carries no more than its bottleneck, so $10M cannot have arrived through a $0.01 transfer. `null` means UNKNOWN (no hop on the path was priced; true of every entry propagated before 2026-08-06) and must not be read as 0, which means 'nothing priced moved'.
          - `via` string, nullable: The address that actually paid this one. null at hops 0 and 1.
          - `via_label` string, nullable: Display name of `via`, same rule as `src_label`.
          - `direction` 'inbound'
        - `indirect_contributors` object[]: Labelled neighbours whose share of the incoming money contributes to `indirect_exposure`.
          - `address` string
          - `label` string, nullable
          - `category` string, nullable
          - `weight` number
          - `inflow_pct` number
          - `contribution` number
          - `direction` string
        - `peel_taint` object: bitcoin only: taint inherited from the ends of a peel (layering) chain this address sits on.
          - `score` number
          - `peel_id` string
          - `hop` integer
          - `depth` integer
          - `origin_category` string, nullable
          - `endpoint_label` string, nullable
        - `suppressed_sources` object: Stored origins dropped as stale: the origin was reclassified after propagation ran.
          - `count` integer
          - `reason` string
        - `policy_suppressed_sources` object[]: Stored exposure that did not score, grouped by category, with the reason. Two reasons exist. (1) The category, bounded by its source's rank, no longer reaches this far. (2) This address is recorded as a `victim` of an incident, and the exposure originates from that same incident: its perpetrator or intermediary addresses, or the incident's own seed (typically gas or a transfer from a drainer to its victim). The reason then reads `this address is recorded as a victim in incident <slug>; ...`, and the slug is the key for `GET /v1/incidents?slug=`. The victim role never removes the address's own seeds, sanction exposure or exposure from any other origin, and does not lift `entity_floor`. Recorded so a lower score than before can be explained.
          - `count` integer
          - `category` string
          - `reason` string
        - `outbound_exposure` object: Payments this address MADE to a designated counterparty. A different claim from everything above it, which measures what the address received — so it is reported here and deliberately does not enter the score. Present only when such a payment exists. A payment is one hop; `hops: 2` appears only with `via_venue_deposit: true`, meaning the money went through the venue's own deposit address.
          - `count` integer
          - `max_score` number
          - `max_value_usd` number, nullable: The largest `value_usd` among the payments below. null when none of them is priced.
          - `reason` string
          - `items` object[]
            - `src` string: The designated address that was paid.
            - `source` string, nullable: Who claims that address, e.g. ofac:sdn_advanced.
            - `category` string, nullable
            - `hops` 1 | 2
            - `score` number
            - `value_usd` number, nullable: Same measure and same caveat as `propagated_sources[].value_usd`: the narrowest hop of the payment path in USD, a ceiling, `null` = unknown.
            - `via` string, nullable
            - `via_venue_deposit` boolean, nullable
            - `direction` 'outbound'
      - `unscorable_reason` 'risk_store_unavailable' | 'chain_label_only': Only with `scored: false`. `risk_store_unavailable`: OURS, not a property of the address — retry the same request. `chain_label_only`: the chain (today `SOL`) is served from attribution alone, there is no graph to score from; permanent, retrying does not help, and it is NOT a clean result.
      - `unscorable_detail` string: Plain-language form of the above.
    - `kyc_origin` string: Block `axes`. The exchange (slug) whose hot wallet made this address's first on-chain funding, i.e. the owner likely passed that exchange's KYC. Provenance, not group membership. Present only when known.
    - `source_of_funds` object: Where the money came from, as CATEGORY buckets — not as addresses and not as a full accounting. The percentages need not sum to 100 and a bucket may legitimately be 0.
      - `basis` 'usd' | 'token_units': `usd`: the percentages are a share of MONEY, each transfer at its own day's rate. `token_units`: the fallback where no prices exist (bitcoin, bsc, tron, unpriced tokens), a share of 18-decimal-equivalent units in which one PEPE weighs like one ETH. Read it before quoting a percentage.
      - `coverage` object: `usd` basis only: how many inbound edges could be priced.
        - `complete` boolean
        - `priced_edges` integer
        - `unpriced_edges` integer
      - `inflow_risk` 'low' | 'medium' | 'high': Verdict about the TOP SOURCES OF INCOMING MONEY, from their own labels only. It is not a verdict about this address: `aml_score` can be CRITICAL from the address's own seed or from propagation while this stays `low`, and that is not a contradiction.
      - `risk_summary` 'low' | 'medium' | 'high': Same value as `inflow_risk`, kept for existing integrations. The old name reads as a verdict about the address, which it never was.
      - `top_sources` object[]
        - `category` string
        - `percentage` number
        - `label` string, nullable
        - `sanctioned` boolean
    - `counterparties` object[]: Block `counterparties`. Top counterparties, up to `top_n`, each with `address`, `label`, `category`, `tokens[]` (per-token sent/received and tx counts), `sent_usd`, `received_usd`, `usd_coverage`, `tx_count`, `first_block`, `last_block`. Self-loops are dropped. Use POST /v1/counterparties for the full picture.
    - `counterparties_meta` object: Block `counterparties`. What the list is a subset OF. The window is scanned and ranked, and only the top `detailed` of it are resolved in full, `ranked_by` value, so small transfers (a mixer deposit, a dust payment) can fall out of a list that looks complete. Walk /v1/address-transactions when completeness matters.
      - `requested` integer
      - `returned` integer
      - `candidate_window` integer: Counterparties ranked.
      - `detailed` integer: Of those, how many were resolved in full.
      - `in_window` integer
      - `ranked_by` 'value'
      - `complete` boolean: True only when nothing was cut.
    - `counterparties_truncated` object: Block `counterparties`. Present only when the STORE cut its own scan: there may be larger counterparties than the ones listed.
    - `behavioral_signals` object[]: Block `counterparties`. Patterns read off the counterparties, e.g. `mixer_adjacent`.
      - `kind` string
      - `severity` string
      - `description` string
      - `evidence` object
    - `inferred` object: Block `counterparties`. Only for an address nobody has labelled: a heuristic reading of its flows (e.g. that it behaves as an exchange deposit address). Our inference, not a claim by a source.
    - `mixer_interaction` object: Block `counterparties`. The address took part in a mixing / coinjoin transaction: contact, not an attribution.
    - `peel_chain` object: Block `counterparties`. bitcoin only: the address is a step of a peel chain.
    - `incidents` IncidentLink[]: Block `incidents` (opt-in). Documented incidents this address appears in, at most a handful; `incidents_total` says how many exist.
      - `slug` string
      - `name` string
      - `category` string, nullable
      - `status` 'confirmed' | 'alleged' | 'disputed' | 'retracted': Anything but 'confirmed' must stay visible to the reader.
      - `stream` 'curated' | 'community': 'curated' is an analyst-recorded investigation; 'community' is a user-filed report. Same shape, NOT the same grade of claim.
      - `occurred_at` string, nullable
      - `summary` string, nullable
      - `link` 'direct' | 'propagated': 'propagated' means the incident belongs to an origin whose risk reached here — it says nothing about this address.
      - `role` 'perpetrator' | 'intermediary' | 'recipient' | 'victim' | 'mentioned' | 'null', nullable: Who this address is to the incident. Only `perpetrator` and `intermediary` accuse: they set `implicates_address` and the link is inherited by whoever the address later pays. `recipient` records that funds of a known origin arrived here and is NOT an accusation — the holder may be a service or an OTC desk rather than a participant; it carries a score but implicates nobody. `victim` and `mentioned` write no seed. A `victim` is not scored for exposure originating from the same incident; see `aml_score_breakdown.policy_suppressed_sources`.
      - `implicates_address` boolean
      - `named_in_source` boolean: false = no published source prints this address; the link is our attribution.
      - `note` string, nullable: Why the link has the role it has, e.g. 'venue (Binance), named by the report but not accused'.
      - `via` object
        - `address` string
        - `hops` integer, nullable
      - `sources` object[]
        - `url` string
        - `title` string, nullable
        - `publisher` string, nullable
        - `tier` 'vendor-advisory' | 'law-enforcement' | 'security-firm' | 'media' | 'social' | 'internal'
        - `published_at` string, nullable
        - `updated_at` string, nullable: Advisories get revised after publication; null when never updated.
        - `archive_url` string, nullable
        - `content_sha256` string, nullable: SHA-256 of the text we read, so a later edit is detectable.
        - `names_addresses` boolean
        - `excerpt` string, nullable
    - `incidents_total` integer: Block `incidents`. How many incidents link this address in total.
    - `degraded` object, nullable: A backing store did not answer, so this entity is INCOMPLETE. A low score on a degraded entity is not evidence of a clean address.
      - `reason` string
      - `calls` string[]
      - `causes` string[]
  - `not_found` object[], required: Addresses that produced no entity, and why. This is NOT a clean bill of health.
    - `wallet_address` string
    - `chain` string: The chain as you sent it.
    - `reason` 'address_not_found' | 'unsupported_chain' | 'invalid_address_format': `address_not_found`: well-formed, but absent from our data. `unsupported_chain`: a chain we do not serve. `invalid_address_format`: the address cannot belong to that chain; `detail` and `hint` say how to fix it. A batch where EVERY address is malformed is a 400 instead.
    - `detail` string
    - `hint` string
  - `degraded` object: Present only when part of this batch could not be scored. The risk store did not answer for those addresses, so they carry `risk.scored: false` and `risk.score: null`, never 0, while their `axes` and `label` are complete and trustworthy. This is OURS, not a property of the address: retry the SAME batch whole. Do not split it; halving a batch on this turns one transient into dozens of failing calls. If NO address in the batch could be scored the response is HTTP 503 instead, with the same body.
    - `unscored` integer
    - `addresses` integer
    - `reason` 'risk_store_unavailable'
    - `detail` string
  - `request_id` string: Quote this when reporting a problem.
  - `processing_time_ms` integer

## Other responses

- `400`: Malformed request.
- `401`: A credential was sent and did not check out: `invalid_api_key`, `key_cannot_sign`, `invalid_signature`, `timestamp_out_of_window`, … Sending no credential at all is not an error.
- `429`: Rate limited. One unit per ADDRESS, not per call: anonymous callers get 100 units per hour per IP (the cheap bucket), a key issued with a quota gets its own. The body names `cost`, `limit`, `window_sec` and `retry_after`; a `Retry-After` header carries the same seconds. Send `X-API-Key` to lift the anonymous limit.
- `500`: Server error.
- `503`: NO address in this batch could be scored: the risk store did not answer for any of them. The body is the ordinary one, `axes` and `label` are still there, `risk.score` is null on every entity, and `error` is `risk_store_unavailable`. Retry the same batch whole after a short pause. A batch where only SOME addresses lost their score answers 200 and reports it in `degraded`.

---

[API](https://skmtc.dev/publicaml/apis/publicaml-intelligence-api.md) · [All operations](https://skmtc.dev/publicaml/apis/publicaml-intelligence-api/llms.txt) · [OpenAPI document](https://skmtc.dev/publicaml/apis/publicaml-intelligence-api/revisions/7c0c6315088c?raw)
