---
title: "Profile clusters by id, or list every address of a category"
method: POST
path: "/v1/entities"
tags: ["Entities"]
---

# Profile clusters by id, or list every address of a category

`POST /v1/entities`

## Request

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

Two modes, selected by which field is sent; exactly one per request (both, or neither, is a 400).

**By id** (`entity_ids`): takes cluster ids, the `axes.membership.group_id` values that /v2/enrich returns, and profiles the whole grouped entity: its size, type, name, and the risk reading of its anchor address (the hot wallet or hub the cluster is keyed on). Use this when the question is about an exchange rather than about one of its addresses. Open to anonymous callers; one unit per id.

A cluster bound to a platform brand (`entity_ref`) also carries `brand`: a summary of the venue as a regulated business — country of registration and country of actual control, the countries that licensed it and the lists those countries are on, and `risk_score`, an integer 0..100. The licences themselves, the KYC periods and the list rows per country are one call away, at GET /v1/brands/{name_key} (`brand.detail`). The score is computed on read, never stored: an active sanction of the ORGANISATION scores 100, one no longer in force 50, a country of the brand on the FATF call-for-action list 100, on the EU high-risk third-country list 50, on the FATF increased-monitoring list 40, on our own weak-supervision list 20. The worst component wins — not the sum, not the average — and the jurisdiction carries over to the brand in full. `null` means no component at all; `complete: false` means the score is a LOWER BOUND, because some country has no list row for the date. The weights and the full rule are published once per response in `brand_risk_policy`. Read `risk_score`, not `high_risk`, which is now derived from it at >= 40 and kept for compatibility.

**By category** (`category` + `chain`): pages through every address attributed to one category on one chain, with a keyset cursor. Each address carries the same facts as `venues[].addresses[]` in /v1/source-trace (label, venue, venue_role, basis, cluster_id, `external_label_meta`) EXCEPT `axes`, which this mode does not return: ask /v2/enrich for the addresses you need them on. Needs an API key (any valid key). Classed with the scans; one unit per address RETURNED: the page size is reserved up front and the unfilled part refunded. Unknown body fields are a 400 in this mode. `deposit` is refused (`category_not_listable`): it has no index a page could walk. Each address also names the platform it belongs to (`brand`): what it scores, where it is registered and managed, which countries licensed it and which lists those countries are on. The paperwork behind that score — the licences themselves, the KYC periods, the list rows per country — is at GET /v1/brands/{name_key}.

## Request body

- object
  - `category` string, required: An attribution category (can_attribute) from GET /v1/categories, e.g. 'gambling'. Not 'deposit'.
  - `chain` 'bitcoin' | 'ethereum' | 'bsc' | 'tron', required: The chain to list. Same values and aliases as everywhere else (ETH, BTC, TRX, BNB accepted).
  - `limit` integer
  - `cursor` string: `next_cursor` of the previous page. Bound to its chain and category; does not expire.
  - `include` string[]: Per-address blocks. Both are on by default; naming a subset turns the other off and [] omits both.

## Response `200`

Entity profiles (by id) or one page of a category's addresses.

- union
  - object
    - `entities` object[]
      - `brand` object: The platform this address or cluster belongs to: what it scores and where it sits, one place per fact. Everything else — the licences, the KYC periods, every ground for the score, the list rows per country, and the developer-facing completeness flags — 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}.
    - `not_found` object[]
    - `brand_risk_policy` object: The scale itself: the weight of every list status, the designation weights, the derived-boolean threshold and the rule texts. Present only when some entity carries `brand`; the same values GET /v1/admin/brands/jurisdictions serves.
      - `as_of` string, date
      - `lists` string[]
      - `closed_lists` string[]: Lists where a missing row means NOT LISTED rather than unknown — our own ones.
      - `list_status_score` object: Keyed `<list>/<status>`.
      - `designation_score` object
      - `high_risk_min` integer
      - `score_rule` string
      - `jurisdiction_rule` string
      - `legacy_rule` string
  - object
    - `entities` unknown[]: Always empty in this mode (envelope kept).
      - unknown
    - `not_found` unknown[]
      - unknown
    - `mode` 'category'
    - `chain` string
    - `category` string
    - `limit` integer
    - `count` integer
    - `addresses` object[]
      - `venue` string, nullable
      - `brand` object: The platform this address or cluster belongs to: what it scores and where it sits, one place per fact. Everything else — the licences, the KYC periods, every ground for the score, the list rows per country, and the developer-facing completeness flags — 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}.
      - `address` string
      - `label` string, nullable
      - `venue_role` 'deposit' | 'hot_wallet' | 'cold_wallet' | 'gas_supplier' | 'treasury' | 'custody' | 'cluster_member' | 'unspecified': Which wallet of the venue this is. `hot_wallet` / `cold_wallet` / `gas_supplier` / `treasury` / `custody` only from the address's OWN role; `deposit` also from the ' (deposit)' naming rule or a deposit-label cluster; `cluster_member` = attributed only by cluster membership; `unspecified` = labelled directly, no role.
      - `basis` object, nullable: Who says this address is this venue in this role.
        - `kind` 'attribution' | 'derived' | 'clustering'
        - `source` string, nullable
        - `confidence` number, nullable
        - `rule` 'entity_role' | 'name_suffix' | 'via_deposit_label'
      - `cluster_id` string, nullable
      - `external_label_meta` object, nullable: What external labels say. `from: anchor` = the address has no label of its own and these rows describe its cluster's anchor (named in `anchor`), not the address.
        - `from` 'address' | 'anchor'
        - `anchor` string, nullable
        - `rows` object[]
          - `source` string, nullable
          - `entity_name` string, nullable
          - `entity_type` string, nullable
          - `website` string, nullable
          - `twitter` string, nullable
          - `entity_role` string, nullable
          - `entity_venue` string, nullable
          - `as_of` string, nullable
          - `labels` string[], nullable
          - `website_deprecated` object, nullable: Set when the venue's published site was checked and is no longer the venue's: `website` is then null and the old URL moves here. Do not render it as a link, least of all when `status` is `hijacked`; where a dead domain redirects is never returned. null for a live site or when no site is known.
            - `url` string: The site as the label source published it.
            - `status` 'dns_gone' | 'unreachable' | 'http_error' | 'parked' | 'hijacked': `dns_gone`: the domain no longer resolves; `unreachable`: it resolves but nothing answers; `http_error`: it answers with an error; `parked`: parked or offered for sale; `hijacked`: it now leads to an unrelated, sometimes fraudulent, site.
            - `checked_at` string, date-time, nullable: When the check that retired it ran.
    - `brand_risk_policy` object: The scale, its weights and its rule texts, once per response. Absent when no address of the page carries a brand.
    - `next_cursor` string, nullable
    - `done` boolean

## Other responses

- `400`: Malformed request.
- `429`: Rate limited: anonymous callers, and keys issued with a quota of their own. Authenticate to remove the limit.
- `500`: Server error.

---

[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)
