---
title: "Transfers touching one address, newest first"
method: POST
path: "/v1/address-transactions"
tags: ["Graph"]
---

# Transfers touching one address, newest first

`POST /v1/address-transactions`

## Request

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

A wallet's transaction feed, with the counterparty and signed direction resolved for each transfer. Optionally annotates each counterparty with its label, or filters the whole window down to transfers with a counterparty of one category ('did this wallet deposit to a casino last month').

The category filter is applied across the entire since/until range, not just the page being returned.

PAGING. Use `cursor`: pass back the `next_cursor` of the previous page and keep going until it is absent. Cost per page does not grow with depth. `offset` still works and is unchanged, but it is a skip, so on a busy address it gets slower the deeper it goes and is not a way to walk a whole history. The two are mutually exclusive in one request.

On bitcoin a page is rounded up to the end of a transaction (the feed's sort key is not unique there), so `count` may come back one or two over `limit`. No row is ever returned twice or skipped.

## Request body

- object
  - `address` string, required: 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.
  - `since` union: Inclusive lower bound. Epoch seconds or ISO-8601 ('2026-06-21').
    - integer
    - string
  - `until` union: Inclusive upper bound.
    - integer
    - string
  - `token` string: Restrict to one token contract. EVM only.
  - `limit` integer
  - `offset` integer: Skip-based paging. Kept for compatibility; prefer `cursor` — a deep offset re-reads everything it skips. Above 100000 the request is a 400 `invalid_parameters` (not pinned: a pinned offset would be a different page); page deeper with `cursor`.
  - `cursor` string: The `next_cursor` of the previous page. '<block>:<tx_hash>' on bitcoin, '<block>:<log_index>:<internal_index>' elsewhere. Refused together with `offset`.
  - `include` string[]: Adds what we know about each counterparty. Unknown values are a 400.
  - `counterparty_category` string: Keep only transfers whose counterparty carries this label category. Implies include: ['counterparty_labels'].

## Response `200`

A page of transfers, newest first.

- object
  - `address` string
  - `chain` string
  - `count` integer
  - `has_more` boolean
  - `next_cursor` string: Pass as `cursor` for the next page. ABSENT at the end of the feed, which is how a loop knows to stop.
  - `nav_window` object: Which block range this page was read from, and whether the address index ran out. Diagnostic: it explains a page's cost without guessing.
    - `from_block` integer
    - `to_block` integer
    - `index_exhausted` boolean
  - `transactions` object[]
    - `tx_hash` string
    - `block_number` string
    - `timestamp` integer: Unix seconds.
    - `direction` 'in' | 'out' | 'self'
    - `counterparty` string: The address on the other side — EXCEPT on bitcoin, where the graph runs address → transaction → address and this is the transaction hash. The wallets are then in `counterparties[]`.
    - `counterparties` string[]: bitcoin only: the wallets on the other side of the transaction.
    - `symbol` string, nullable
    - `decimals` integer
    - `value` number, nullable: Human-readable amount. null, or computed with a fallback of 18 decimals, for tokens outside the known-token registry — prefer `amount_raw` then.
    - `amount_raw` string: Exact amount in the token's smallest unit, as a decimal string.
    - `counterparty_labels` object[]: Opt-in. An empty array means no label is held, not that it was not looked up.
      - `address` string
      - `label` string, nullable: Display name of the entity, e.g. 'Binance 14'.
      - `category` string, nullable: Entity type: cex, dex, mixer, gambling, bridge, payment, …
      - `source` string, nullable: The feed behind the claim, named as it is stored — e.g. `ofac:sdn_advanced`, `arkham:sanctioned`, `chainabuse:chainabuse`. Every source is named: address answers withheld non-public feeds behind the literal `publicaml` until 2026-09-11, and no longer do. How much the claim is worth is `source_confidence`, published alongside. A null means the stored claim never carried a source at all — not that one is being hidden.
      - `source_confidence` number, nullable: How much we trust whoever made this claim, 0..1. Derived from the source's tier: an official list answers 1.0, a vendor blocklist 0.5, a community report 0.25. Absent when no tier prices the source — that is a gap in our policy table, not a statement of trust, so it is not reported as confidence.
      - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not the same as source_confidence, which is ours about the source.
      - `risk_score` number
      - `sanctioned` boolean
      - `sanction` object
        - `label` string, nullable
        - `source` string, nullable
  - `matched_counterparties` object[]: With `counterparty_category`: the labelled venues actually hit in the window.
    - `address` string
    - `label` string, nullable: Display name of the entity, e.g. 'Binance 14'.
    - `category` string, nullable: Entity type: cex, dex, mixer, gambling, bridge, payment, …
    - `source` string, nullable: The feed behind the claim, named as it is stored — e.g. `ofac:sdn_advanced`, `arkham:sanctioned`, `chainabuse:chainabuse`. Every source is named: address answers withheld non-public feeds behind the literal `publicaml` until 2026-09-11, and no longer do. How much the claim is worth is `source_confidence`, published alongside. A null means the stored claim never carried a source at all — not that one is being hidden.
    - `source_confidence` number, nullable: How much we trust whoever made this claim, 0..1. Derived from the source's tier: an official list answers 1.0, a vendor blocklist 0.5, a community report 0.25. Absent when no tier prices the source — that is a gap in our policy table, not a statement of trust, so it is not reported as confidence.
    - `confidence` number, nullable: The source's OWN stated certainty, untouched. Not the same as source_confidence, which is ours about the source.
    - `risk_score` number
    - `sanctioned` boolean
    - `sanction` object
      - `label` string, nullable
      - `source` string, nullable
  - `counterparty_scan` object: How much of the window the label scan covered. `truncated: true` makes the match count a lower bound.
  - `request_id` string
  - `processing_time_ms` integer

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