---
title: "Walk an address's funding backwards to find who paid it"
method: POST
path: "/v1/source-trace"
tags: ["Tracing"]
---

# Walk an address's funding backwards to find who paid it

`POST /v1/source-trace`

## Request

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

The mirror of /v1/trace: walks INCOMING edges backwards to answer 'where did this wallet's money come from'. Any labelled entity is a dead end (the trail stops at the exchange rather than walking into its other customers), and so is an unlabelled wallet whose own degree makes it a service — otherwise a single dispenser pulls in everyone who ever used it.

One call replaces a client-side per-hop BFS. Long walks return `done: false` with a `cursor`; pass it back to continue the SAME traversal (state lives server-side for an hour). `hop_trace` is the delta for that call, `source_links` and `nodes_visited` are cumulative.

USD valuation is left to the caller: every flow carries `value_raw` plus a per-token breakdown.

`venues` groups every casino (`gambling`) address the walk met by BRAND: 'Stake.com' and 'Stake.com (deposit)' are one venue, while in `source_links` they stay two links (that list keeps its shape). Brands are matched as exact strings, not normalised. Each address says which wallet of the venue it is (`venue_role`) and on what basis; `hot_wallet` is only ever the address's own role, never a label copied from its cluster's anchor. No attribution axes here: take them from /v2/enrich for the addresses you need.

## Request body

- object: Any numeric field outside its range is pinned to it and named in `clamped`; a non-number is a 400 `invalid_parameters`.
  - `address` string, required: The address whose funding to trace. 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.
  - `chain` 'bitcoin' | 'ethereum' | 'bsc' | 'tron', required: Which blockchain the address belongs to. Send one of the four 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.
  - `max_hops` integer: How far back to walk.
  - `max_width` integer: Funders to expand per address, largest first.
  - `max_nodes` integer: Hard ceiling. Hitting it ends the walk with truncated: true.
  - `budget_sec` number: Wall-clock ceiling for this call's walk. Default 8 s (40 s on bitcoin).
  - `top_n` integer: Counterparties in the root profile.
  - `expand_width` integer: How many hop-1 funders to walk back from. Defaults to max_width.
  - `direct_width` integer: How many direct funders to report at hop 1.
  - `surface_categories` string[]: Which labelled entity types to aggregate into `source_links`. Default ['gambling'].
  - `stop_at_hubs` boolean: Stop at unlabelled high-degree addresses. Turning this off will pull in unrelated wallets.
  - `cursor` string: Resume token from a prior `done: false` response.

## Response `200`

The backward funding trace.

- object
  - `root` object: The subject's own profile — score, counterparties, source of funds.
  - `source_links` object[]: Labelled funding sources found, aggregated. Cumulative across resumed calls.
  - `venues` object[]: Casino view by brand. Always present; `[]` when the walk met no `gambling` address, whatever `surface_categories` says. Cumulative across resumed calls, like `source_links`. Sorted by `flow_raw`, largest first.
    - `venue` string, nullable: Brand: the attributed name without the ' (deposit)' suffix. Exact string, not normalised.
    - `venue_meta` object, nullable: The venue's public links from its label, when a source published any.
      - `website` string, nullable: The venue's live site. null when none is known or when it was retired (see `website_deprecated`).
      - `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.
      - `twitter` string, nullable
    - `hop` integer: Nearest hop of any of its addresses.
    - `flow_raw` string: Sum of the addresses' `flow_raw` (18-dec cross-token weight, not a coin amount).
    - `addresses` object[]
      - `address` string
      - `hop` integer
      - `label` string, nullable
      - `flow_raw` string: The edge value this address sent toward the traced wallet: the same number folded into `source_links`.
      - `tokens` object[]
      - `source_link` string, nullable: `name` of the `source_links` entry holding this address, or null if it was not surfaced.
      - `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.
  - `hop_trace` object[]: Nodes visited by THIS call only.
  - `nodes_visited` integer
  - `hub_stops` integer: Addresses not expanded because their degree made them a service.
  - `truncated` boolean
  - `done` boolean: false = more remains; re-send with `cursor`.
  - `cursor` string, nullable
  - `effective` object: The parameters actually used.
  - `clamped` string[]: Parameters that were reduced to their allowed range.

## Other responses

- `400`: Malformed request. A numeric parameter that is not a number answers `invalid_parameters`: `invalid[]` names every bad field with the reason and the accepted range, and `limits` lists the ranges of all numeric fields of this endpoint. A number OUTSIDE its range is not an error: it is pinned to the range and named in `clamped` (where the response has one).
- `429`: One of two things, told apart by `error`: * `rate limit exceeded` — your hourly quota (anonymous callers, and keys issued with a quota of their own). Wait `retry_after` seconds; authenticating removes the limit. * `server_busy` — this endpoint is a graph walk or a range scan, and the server is already running as many of those at once as it admits. Nothing was charged for the work. Retry the SAME request after `Retry-After` seconds (a few); do not split it into smaller calls, that multiplies the load that caused the refusal.
- `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)
