# Scanner Docs (full)
> Full documentation for Scanner (https://www.solscanner.app/). Page index: https://www.solscanner.app/llms.txt
# Scanner Docs
URL: https://www.solscanner.app/docs
On-chain intelligence for Solana and Robinhood Chain. Explore, deep-scan, trace, and embed.
Scanner is on-chain intelligence for Solana and Robinhood Chain. It traces funds, maps the connections between wallets, and exposes coordinated groups ("bundles") behind a token or wallet.
Where to start [#where-to-start]
The two chains [#the-two-chains]
The Explorer, Deep Scan, and Wallet Finder run on both chains, with the same layout and search flow. The detail views adapt to each chain's data model. Some features are Solana only: Seek, the KOL tracker, and parts of Deep Scan such as KOL enrichment, top traders, AI analysis, export, cross-chain traces, and `.sol` domains. See [Solana and Robinhood Chain](/docs/scanner#solana-and-robinhood-chain) for the full list.
| Chain | Explorer | Deep Scan | Wallet Finder |
| --------------- | -------------- | ------------------------------ | ---------------------------- |
| Solana | [`/sol`](/sol) | [`/sol/scanner`](/sol/scanner) | [`/sol/finder`](/sol/finder) |
| Robinhood Chain | [`/rh`](/rh) | [`/rh/scanner`](/rh/scanner) | [`/rh/finder`](/rh/finder) |
Get access [#get-access]
The Explorer, Wallet Finder, and the trackers are free and need no account. Deep Scans and Seek run on your plan's scan allowance, and every account gets 30 free scans a month. See [Plans and access](/docs/access) for what each scan costs, plus API keys, Enterprise, and the support channel that comes with them.
---
# Explorer
URL: https://www.solscanner.app/docs/explorer
Browse transactions, wallets, tokens, and blocks on Solana and Robinhood Chain, free and with no sign-in.
The Explorer is the free, no-sign-in way to look up anything on-chain. A single search box takes a transaction, a wallet or address, a token, or a block, detects what you pasted, and routes you to the right detail view.
It runs on two chains today:
* **Solana Explorer**, at [`/sol`](/sol)
* **Robinhood Explorer** for Robinhood Chain (an EVM chain), at [`/rh`](/rh)
Both explorers share the same layout and search flow. The detail views adapt to each chain's data model.
What you can look up [#what-you-can-look-up]
Transactions [#transactions]
* **Solana:** full instruction breakdowns, pre/post balance changes, program logs, and human-readable action summaries.
* **Robinhood Chain:** decoded method calls, internal-transaction traces, token transfers, event logs, plus status and gas.
Wallets and addresses [#wallets-and-addresses]
Balances, token holdings, and transaction history. Each wallet also gets an **Origin of Funds** tab showing who funded it and the original source behind that. Connected wallets and bundle groups are not part of the Explorer: they come from a [Deep Scan](/docs/scanner), which needs an account. On Robinhood Chain, address views also show ETH balance and whether the address is a verified contract.
Tokens [#tokens]
Live market stats, security and rug checks, liquidity pools, top holders, and recent trades, for any Solana mint or any Robinhood Chain ERC-20.
Blocks [#blocks]
* **Solana:** a slot's transactions with the fee breakdown (priority fee, Jito tip, total), compute units used and CU price, SOL moved, and result.
* **Robinhood Chain:** a block's transactions with gas, base fee, and validator details.
Origin of funds [#origin-of-funds]
Address and transaction views answer the question "where did this wallet's money originally come from" without leaving the Explorer.
* **Origin card:** on an address page, an origin section shows the first known entity behind the wallet's funding, for example "originally funded by Binance", with the hop count. Expand it to walk the chain funder by funder.
* **Transaction origin:** on a transaction page, each signer gets the same treatment, so you can see at a glance whether the actors were funded from an exchange, a protocol, or an unlabeled wallet.
* **Origin chips:** small "via ..." badges appear next to counterparty addresses when their origin is already known.
Results Scanner has already computed appear instantly. Fresh traces run live and are rate-limited per visitor to a handful every few minutes, so heavy tracing belongs in a [Deep Scan](/docs/scanner) or a [Seek investigation](/docs/seek).
Explorer vs. Deep Scan [#explorer-vs-deep-scan]
The Explorer is read-only browsing: fast, free, and open to everyone. The **Scanner** ("Deep Scan") is the analysis layer on top of the same data. It maps the *relationships* between wallets: who funded whom, which wallets move together, and which look coordinated. See [Deep Scan](/docs/scanner) for what it reveals.
---
# URL reference
URL: https://www.solscanner.app/docs/urls
Every explorer view has a stable, shareable URL. The full pattern list for both chains, plus the drop-in mapping from Solscan-style links.
Every explorer view has a stable URL you can share, bookmark, or template into your own tools. This page lists all of them, along with the Deep Scan and Seek routes, and shows how to swap an existing Solscan-style link for a Scanner one.
All paths below are relative to `https://www.scanner.net`.
Solana [#solana]
Solana identifiers are base58 strings: transaction signatures, wallet addresses, and token mints.
| View | Pattern |
| ------------------------------------------------------------ | ----------------------------- |
| Explorer hub and search | `/sol` |
| Transaction | `/sol/tx/{signature}` |
| Address or wallet | `/sol/address/{address}` |
| Token | `/sol/token/{mint}` |
| Block | `/sol/block/{slot}` |
| Address or token, kind unknown (redirects to the right view) | `/sol/go/{value}` |
| Wallet Finder | `/sol/finder` |
| Deep Scan | `/sol/scanner` |
| Deep Scan of an address | `/sol/scanner/{address}` |
| Full-screen bubble map | `/sol/scanner/{address}/map` |
| Seek, starting from an address | `/sol/scanner/{address}/seek` |
| KOL tracker | `/sol/kol` |
`/sol/go/{value}` is for links where you don't know whether the value is a wallet or a token mint. It works out which one it is and redirects. A transaction signature redirects straight to the transaction view.
The bubble map takes an optional mode in the query string: `?mode=quick`, `?mode=deep`, or `?mode=max` for a wallet scan at that depth, and `?mode=holders` or `?mode=traders` for a token scan. With no mode it detects whether the address is a wallet or a token. The Seek view takes `?quick` or `?max` for the depth and defaults to Deep.
Robinhood Chain [#robinhood-chain]
Robinhood Chain is an EVM chain, so identifiers are 0x-prefixed: 66-character transaction hashes and 42-character addresses.
| View | Pattern |
| ----------------------- | --------------------------- |
| Explorer hub and search | `/rh` |
| Transaction | `/rh/tx/{hash}` |
| Address or wallet | `/rh/address/{address}` |
| Token | `/rh/token/{address}` |
| Block | `/rh/block/{number}` |
| Wallet Finder | `/rh/finder` |
| Deep Scan | `/rh/scanner` |
| Deep Scan of an address | `/rh/scanner/{address}` |
| Full-screen bubble map | `/rh/scanner/{address}/map` |
The Robinhood Chain bubble map accepts the same `?mode=` values except `traders`.
Seek [#seek]
[Seek](/docs/seek) investigations aren't chain-prefixed.
| View | Pattern |
| ------------------------------ | ------------------- |
| Seek home and saved workspaces | `/seek` |
| Shared workspace | `/seek/shared/{id}` |
Shared workspace links are created from a workspace's share dialog. What someone can do with one depends on how it was shared.
Coming from Solscan [#coming-from-solscan]
Replacing Solscan links is a domain swap. The paths map one to one, with a single naming difference: Solscan says `account`, Scanner says `address` (both work here).
| Solscan | Scanner |
| ------------------------------ | ------------------------ |
| `solscan.io/tx/{signature}` | `/sol/tx/{signature}` |
| `solscan.io/account/{address}` | `/sol/address/{address}` |
| `solscan.io/token/{mint}` | `/sol/token/{mint}` |
| `solscan.io/block/{slot}` | `/sol/block/{slot}` |
You don't even need to add the `/sol` prefix. Bare Solscan-style paths redirect to the right chain-scoped view automatically:
* `/tx/{id}`, `/address/{id}`, `/account/{id}`, `/token/{id}`, and `/block/{id}` all redirect.
* 0x identifiers route to the Robinhood Chain explorer, everything else routes to Solana. Bare `/block/{n}` assumes Solana, since block numbers look the same on both chains.
So if a bookmarklet, browser keyword search, or bot templates `solscan.io/tx/{signature}`, pointing it at `www.scanner.net/tx/{signature}` just works.
Not sure what you have? [#not-sure-what-you-have]
Paste anything into the search box on [`/sol`](/sol) or [`/rh`](/rh). It detects whether you pasted a transaction, address, token, or block and routes you to the right view.
Machine-readable index [#machine-readable-index]
For AI tools and crawlers:
* [`/llms.txt`](/llms.txt): site and docs index with these URL patterns.
* [`/llms-full.txt`](/llms-full.txt): the full documentation as one plain-text file.
---
# Deep Scan
URL: https://www.solscanner.app/docs/scanner
What the Scanner reveals, including funding traces, connected wallets, and bundle analysis for any wallet or token.
The Scanner (a **Deep Scan**) is the analysis layer. Paste any wallet or token and it auto-detects which one it is, then builds a map of the on-chain relationships around it: where the money came from, which wallets move together, and which groups look coordinated.
It's available on both chains:
* **Solana:** [`/sol/scanner`](/sol/scanner)
* **Robinhood Chain:** [`/rh/scanner`](/rh/scanner)
Deep Scans require sign-in. Browsing individual transactions, wallets, tokens, and blocks stays free in the [Explorer](/docs/explorer).
Auto-detection [#auto-detection]
You don't pick a mode. Paste an address and the Scanner detects whether it's a **wallet** or a **token** and runs the matching analysis.
Scan depth and cost [#scan-depth-and-cost]
A wallet scan can run at three depths. Depth controls how far the graph expands outward from the starting wallet. Greater depth follows more hops and surfaces more related wallets, and takes longer to complete.
| Depth | What it's for | Cost |
| ----- | ------------------- | ------- |
| Quick | A fast first look | 1 scan |
| Deep | The standard trace | 2 scans |
| Max | An exhaustive trace | 4 scans |
"Deep Scan" is the name of the product as a whole. **Deep** in this table is one of its three depths, the middle one. A Quick or Max scan is still a Deep Scan.
Token scans cost 1. If a scan fails after being charged, the scans are refunded automatically. Repeat scans of the same target are faster because results are cached. See [Plans and access](/docs/access) for how allowances work.
What a wallet scan reveals [#what-a-wallet-scan-reveals]
* **Funding sources:** traces where the wallet's balance came from, walking back through the chain of funders toward the original source or sources.
* **Connected wallets:** other wallets linked to it through funding and transfer activity.
* **Bundles:** clusters of wallets that appear to act together rather than independently. Coordinated wallets are grouped into labeled **bundle groups**.
* **Insiders and known entities:** flags wallets tied to a token's deployer or liquidity, and labels known exchanges and infrastructure so they don't read as organic activity.
* **Holdings and context:** the wallet's current holdings, plus any names or notes you've saved.
On **Solana**, a wallet scan additionally surfaces **KOL wallets** (known influencer wallets) within the graph, and lists the wallet's top trades.
What a token scan reveals [#what-a-token-scan-reveals]
* **Token profile:** market stats, security checks, and liquidity pools.
* **Top holders with bundle detection:** the largest holders, flagged where they belong to a coordinated bundle rather than being independent.
* **Bundle map:** the relationship graph across holders and the wallets connected to them.
On **Solana**, a token scan can also rank **top traders by realized PnL**.
Solana and Robinhood Chain [#solana-and-robinhood-chain]
The core scan (auto-detection, the three depths, funding sources, connected wallets, bundles, and the bubble map) works the same on both chains, and so does a multi-token scan of 2 to 5 token contracts. These features are Solana only:
* KOL wallets in scan results
* Top traders mode for token scans, and a wallet's top trades
* AI analysis of a scan
* Exporting scan results
* Cross-chain traces
* `.sol` domain search
* The [Seek](/docs/seek) view
The bubble map [#the-bubble-map]
Results render as an interactive **bubble map**. Nodes are wallets and tokens; links are the funding and transfer relationships between them. Color encodes each node's role: the wallet you searched, its funders, bundled wallets, liquidity, and so on. You can zoom, pan, filter, and expand any node to dig deeper.
A note on method [#a-note-on-method]
This page describes *what* a Deep Scan surfaces, not *how* it's computed. The specific signals, thresholds, and heuristics behind funding, bundle, and insider detection are proprietary and intentionally undocumented.
---
# Wallet Finder
URL: https://www.solscanner.app/docs/finder
Recover a full wallet address from the truncated form shown in feeds, PnL cards, and screenshots. Free, no sign-in.
Feeds, PnL cards, and screenshots usually truncate addresses to something like `7xKq...pump`. The Wallet Finder takes the characters you can see and recovers the full address from an index of on-chain activity.
It's free, needs no sign-in, and runs on both chains:
* **Solana:** [`/sol/finder`](/sol/finder)
* **Robinhood Chain:** [`/rh/finder`](/rh/finder)
You don't have to visit those pages to use it. Paste a truncated snippet straight into the main search box on [`/sol`](/sol) or [`/rh`](/rh) and the finder answers inline, right under the search bar.
How to search [#how-to-search]
Type the first and last characters you can see. Any of these work:
* **Start and end:** the strongest search. Paste the truncated form as-is (`7xKq...pump`, `7xKq*pump`, `7xKq pump`) and it splits into the two fields for you.
* **Start only** or **end only:** works too, just expect more matches. Fill in one field, or in the main search box mark which one it is with a gap: `7xKq...` for a start, `...pump` for an end.
* **A fragment from the middle:** in the main search box, characters typed with no gap match anywhere in the address.
Each part needs a minimum number of characters:
| Search | Solana | Robinhood Chain |
| --------------- | ------ | --------------- |
| Start only | 3 | 4 |
| End only | 4 | 4 |
| Start and end | 3 each | 4 each |
| Middle fragment | 4 | 6 |
On Robinhood Chain, a leading `0x` marks the characters as the start of an address and doesn't count toward the minimum.
Results update as you type. Each match shows the full address, its KOL or exchange label when one is known, plus one-click jumps to its [Explorer](/docs/explorer) page or a [Deep Scan](/docs/scanner).
The finder allows 30 searches a minute per chain from one IP address. Because results update as you type, a burst of edits counts toward that. If you hit the cap, wait a minute and search again.
What's in the index [#whats-in-the-index]
The index covers addresses seen in recent on-chain activity and grows as more is observed. Addresses drop out again after a long period of inactivity. A wallet that has never appeared in indexed activity won't match yet. When several addresses share the same visible characters, the most recently active ones are shown first: add more characters to narrow it down.
---
# Seek
URL: https://www.solscanner.app/docs/seek
An investigation workspace that follows funding flows hop by hop, across wallets, exchanges, and bridges.
Seek, at [`/seek`](/seek), is the investigation workspace. Where a [Deep Scan](/docs/scanner) gives you a one-shot map around a single target, Seek lets you follow the money interactively: expand a wallet, see who funded it, follow that funder, and keep going until the trail ends at an exchange, a bridge, or a dead end.
What it does [#what-it-does]
* **Hop-by-hop tracing:** start from a Solana wallet, token, or `.sol` domain and expand the funding graph node by node.
* **Entity labeling:** wallets along the trail are labeled as exchanges (CEX), bridges, KOLs, and other known entities, so you can tell infrastructure from actors.
* **Cross-chain trails:** when funds leave through a bridge, Seek can pick up the trail on the other side, so a trace doesn't stop at the chain boundary.
An investigation always starts on Solana. A Robinhood Chain address can't start a Seek workspace.
Workspaces [#workspaces]
Every Seek session is a **workspace** you can save and come back to. Workspaces live in your dashboard under [Investigations](/dashboard/investigations), where you can:
* rename and delete them,
* share them by link, either publicly or restricted,
* invite a person by their email address or wallet address (they need a Scanner account),
* give collaborators **Editor** (can expand and annotate) or **Viewer** access.
Access [#access]
Seek requires sign-in. Each live expansion spends 1 scan whatever its depth, and re-expanding something already traced is free for a day.
In a shared workspace, whoever runs an expansion pays for it. An Editor's expansions spend the Editor's own scans, not the owner's. See [Plans and access](/docs/access).
---
# KOL tracker
URL: https://www.solscanner.app/docs/trackers
A leaderboard of known influencer wallets on Solana, ranked by PnL. Free to browse.
A free leaderboard tracks which known wallets are actually making money on Solana. It refreshes every 6 hours, is searchable, browsable without an account, and lets you jump any wallet straight into the [Explorer](/docs/explorer) or a [Deep Scan](/docs/scanner).
KOL Wallets [#kol-wallets]
[`/sol/kol`](/sol/kol) is a curated directory of over a thousand known and suspected **KOL** (key opinion leader) wallets, each tied to an identity such as a Twitter handle. Sort by PnL over 24h, 7d, or 30d, search by name, handle, or address, and expand any card for the detailed chart.
The directory is community-fed:
* **Apply:** submit a KOL to be listed.
* **Dox:** report a wallet you believe belongs to a KOL.
KOL identities also show up elsewhere in Scanner: inside Deep Scan graphs on Solana and in your scan history, flagged wallets carry their KOL name and handle.
---
# Indexer overview
URL: https://www.solscanner.app/docs/indexer/overview
A read-only chain node, indexer, and HTTP read API. Stores canonical chain records and serves searchable views over them, without ever signing or sending a transaction.
The Indexer is the data layer the rest of Scanner is built on. It runs its own chain node, keeps the records that node confirms, and serves them over an HTTP read API.
It is read-only by construction. It holds no keys, signs nothing, and sends no transactions. The only thing it does with a chain is observe it.
Today it indexes Robinhood Chain. It was built so a second chain is configuration plus an ingest adapter rather than a rewrite, and more chains are coming. See [Chains](/docs/indexer/chains) for what that means for callers.
Speculative and confirmed records [#speculative-and-confirmed-records]
The Indexer keeps what it has seen early apart from what it has verified, on purpose.
An **early observation** is published within seconds, before execution is settled. A record is marked **confirmed** only once its canonical receipt has been verified.
The rule that falls out of this is the single most important thing to understand about the data:
> A record seen early may revert. Swaps, transfers, approvals, deployments, and launch signals carry speculative provenance until a canonical receipt confirms them. Only a confirmed record can mark an attempt successful.
Latency and correctness are separate. A delay in observation costs freshness, and never promotes a speculative record into a confirmed one.
What it serves [#what-it-serves]
115 routes, grouped by the thing you are asking about.
| Group | What you get |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Tokens | Identity, supply, holders, transfers, swaps, candles, market cap, all-time high and low, launch lifecycle |
| Pools | Pool identity, swaps, ticks, candles, liquidity, rolling statistics, including Uniswap V4 keyed by manager and pool id |
| Wallets | Holdings, portfolio, positions and PnL, trades, transfers, transactions, unified activity, funding graph |
| Chain | Blocks, transactions, receipts, logs, decoded logs and calldata, internal transfers |
| Screener and rankings | Filtered and sorted views over tokens and pools, with every ranking publishing its own formula |
| Cohorts | Wallet clusters, their members, and the evidence edges that produced them |
| Streams | Live subscriptions to heads, blocks, transactions, logs, transfers, AMM events, creations, launches, and live candles |
| Addresses and contracts | Address type, labels, runtime code observation |
| Live RPC | Read-through to the node for head, code, balance, and token metadata right now |
Full paths and response fields are in the [API reference](/docs/indexer/api).
What it will not do [#what-it-will-not-do]
It states these limits rather than papering over them, which is worth knowing before you build on it.
* It does not merge quote units. A token trading against wrapped native and against a stablecoin has two separate series, and nothing sums them.
* It does not invent labels. A label is published only where the Indexer has evidence for it.
* It does not guess. A value it could not derive is null with a reason, never zero and never omitted.
* It does not serve a partial answer as a complete one. A route whose backing data is still building, disabled, or stale refuses rather than under-reporting.
[What it stores](/docs/indexer/data) covers those rules in detail, because they change how you read a number.
---
# Chains
URL: https://www.solscanner.app/docs/indexer/chains
Robinhood Chain today, more coming. How chain scoping works, and what stays separate per chain.
Every data route is chain-scoped. The chain is part of the path, and it is required:
```
GET /index/v1/{chain}/tokens/{address}
```
`{chain}` is a slug the deployment is configured to serve, not a raw chain id. Asking for a slug it does not serve returns a typed refusal that lists the slugs it does serve. It never quietly falls back to a default chain, because answering the wrong chain's data is worse than answering nothing.
Available today [#available-today]
| Chain | Slug | Chain id | Native asset | Status |
| --------------- | ----------- | -------: | ----------------- | ------- |
| Robinhood Chain | `robinhood` | 4663 | ETH (18 decimals) | Serving |
More chains are planned. The boundary was designed up front so adding one is configuration and an ingest adapter, not a rewrite.
The current Robinhood deployment ingests from the chain's sequencer feed, with sequencer-ordered
finality and a bounded maximum reorg depth. These are deployment properties, not assumptions a
client should duplicate; read the snapshot and coverage fields returned by each response.
What a chain declares [#what-a-chain-declares]
A chain is not just an id. Each one declares the following, and the deployment refuses to start if a chain is only half-declared. A half-declared chain would not return no answers, it would return plausible wrong ones.
| Property | Meaning |
| --------------- | ------------------------------------------------------------- |
| Ingest adapter | How blocks arrive, whether a sequencer feed or plain JSON-RPC |
| Finality model | Whether the chain exposes tiers beyond its head |
| Reorg depth | How far the chain may rewind, which bounds cursor validity |
| Native asset | Symbol and decimals |
| Wrapped native | The ERC-20 wrapper, required for USD marking |
| AMM authorities | Which pool-creating protocols this build recognises |
| Launchpad table | Which launchpads this build decodes |
Anything not declared is absent rather than assumed. A deployment with no AMM authorities configured serves no pool identity at all, and the routes that depend on it say so instead of inventing a venue.
What stays separate per chain [#what-stays-separate-per-chain]
This is the part that matters when the second chain arrives, and it is worth building against now.
**Separate stores.** Each chain has its own index, its own reorg state, and its own cursors. A cursor minted against one chain is not valid on another and will be refused.
**Separate snapshots.** Each chain has its own head and its own reorg timeline, so there is no such thing as one `(block, hash)` spanning two chains. A multi-chain response carries one snapshot per chain plus a per-chain status.
**No silent omission.** A response that was asked for three chains covers exactly three chains. A chain that could not be reached is a typed unavailable, never dropped from the result. An omitted chain would read as a chain with no data, which is a different and false claim.
If you are writing a client today against one chain, the thing to get right now is that the chain segment is not decorative and the snapshot is per chain. Code that assumes a single global head will need changing later; code that reads the snapshot off each chain's own block will not.
---
# What it stores
URL: https://www.solscanner.app/docs/indexer/data
How a number was derived, and the rules that decide what a value means. Absence, quote units, transfer-derived balances, labelled approximations, and trace coverage.
These rules apply to every route. They are the difference between reading a number correctly and reading it confidently but wrong.
Absent is not zero [#absent-is-not-zero]
A value that could not be derived is null and carries a reason. It is never zero, and it is never dropped from the response.
This matters most where zero is a plausible answer:
| Value | Means |
| -------------- | ---------------------------------------------------- |
| `volume: 0` | The window was computed, and no trades were observed |
| `volume: null` | The window could not be computed |
The same distinction holds for decoding. A contract that could not be decoded is reported as undecoded, not as a contract with nothing to report.
Quote units are never merged [#quote-units-are-never-merged]
A token trading against wrapped native and against a stablecoin has two separate series. No route sums them. That includes market cap, liquidity, and portfolio totals.
If you want one blended number you have to make that choice yourself, with the quote basis visible. The Indexer will not make it silently, because the blend is an editorial judgment and the inputs are not interchangeable.
Balances are transfer-derived [#balances-are-transfer-derived]
Balances are the running signed sum of decoded transfers. They are not `balanceOf` calls against the node.
One consequence is worth stating plainly: a negative balance means inbound history is incomplete, and it is served as-is rather than clamped to zero. Clamping would hide the gap and present a wrong number as a clean one.
A wallet holdings row exists only while the balance is non-zero, so holdings routes are what a wallet holds now, not what it has ever held.
Approximations say so [#approximations-say-so]
Where a value is a bounded estimate rather than an exact count, it is labelled and published with its bounds.
* Distinct-trader counts are a bounded sketch, published with exact lower and upper bounds.
* Top-holder lists are exact. `/top-holders` pages a balance-ordered index that is updated in the same write as every balance, with no cap and a cursor. The one remaining capped, approximate holder list is internal: it only picks the candidate wallets for `/traders`, and that route says so.
* Compact amounts carry a precision witness so you can tell what was preserved.
Internal transfers are the one unverifiable class [#internal-transfers-are-the-one-unverifiable-class]
Internal value transfers come from node re-execution, not from a consensus commitment. They are the only data class in the store that cannot be checked against a block root.
Routes that use them publish their own coverage and gap counts rather than presenting them at the same confidence as receipt-backed data. Treat them accordingly.
Coverage is stated per route [#coverage-is-stated-per-route]
Routes whose underlying data is known to be partial carry that fact on every response, as a fixed field describing the route's limits rather than that particular answer.
| Route family | Carries |
| ------------------- | -------------------------------------------------------- |
| Wallet transactions | Top-level transactions only, internal calls not retained |
| Transfers | Transfer activity only, not complete EVM history |
| Trace-derived | Pending and gapped block counts |
Empty is a real answer [#empty-is-a-real-answer]
A `200` with empty collections usually means the subject genuinely has no matching rows. Discovery feeds in particular, such as new tokens and new pools, are unfiltered lifecycle feeds and promise no activity.
Before treating an empty result as a coverage bug, check it against a subject you know is populated. The two look identical from a single call.
Coverage while projections backfill [#coverage-while-projections-backfill]
Some projections can still be backfilling while the canonical head advances. During that window,
wallet activity may describe a recent interval rather than full history, and portfolio responses may publish a typed
absence for `totalUsd` while still returning the coordinate split. Always honor each route's
`coverage`, `status`, and reason fields; do not interpret a missing historical row as proof that the
wallet never held the asset.
---
# Read API
URL: https://www.solscanner.app/docs/indexer/api
URL shape, the behaviour every route shares, and the route groups. Pagination, snapshots, precision and absence are documented once rather than per endpoint.
Every data route lives under a chain-scoped prefix:
```
GET /index/v1/{chain}/{path}
```
So the token detail route written as `/tokens/{address}` is served at `/index/v1/robinhood/tokens/{address}`.
The API host is deployment-specific. The published contract carries only a placeholder host,
so configure clients with the host supplied for the deployment instead of hard-coding an example URL.
Authentication and limits [#authentication-and-limits]
Protected routes take an `x-api-key` header carrying the scope for that route class. A valid key
without the required scope receives `403 insufficient_scope` and is not charged; a missing or
unknown key receives `401`. The public probes `GET /health/live` and `GET /health/ready` do not
require a key. Prometheus `GET /metrics` is only available on the separate loopback metrics listener.
Indexer keys are separate from Scanner API keys. A dashboard key beginning with `sk_live_`
([API keys](/docs/api/keys)) calls the Scanner API and does not authorize the indexer.
Route limits are per key. They refill continuously rather than resetting at a fixed
minute boundary. A limited response includes `RateLimit-Limit`, `RateLimit-Remaining`, and
`RateLimit-Reset`; a `429 rate_limited` response includes `Retry-After`. A route class with no
configured limit for your key sends none of these headers. Stream connections have a separate
active-connection ceiling per key. Opening one stream too many also answers `429 rate_limited`, but
without `Retry-After`: close a stream you no longer need rather than waiting. Prefer one multiplexed
`/stream/subscribe` connection when watching several topics.
Read this before the route list [#read-this-before-the-route-list]
Pagination, ordering, snapshots, precision, USD basis and absence work the same way on every route. They are specified once, and the route pages do not repeat them. A reader who skips straight to a path is the one most likely to misread a response.
Snapshots [#snapshots]
Every response carries one canonical `(block, hash)` pair. Every row in it was read at that block.
A paginated walk keeps the same anchor across pages, so page three cannot contain something that did not exist when page one was served.
Cursors are fork-bound [#cursors-are-fork-bound]
A cursor expires when its fork is gone, not on a timer. If the chain reorgs past the point a cursor was minted at, that cursor is refused with a typed error rather than resumed against a different history.
Cursors are also chain-scoped. One minted against one chain is not valid on another.
Failing closed [#failing-closed]
A route backed by a projection that is still building, disabled, or stale returns a typed refusal. It does not serve a partial answer that would read as complete.
Pagination bounds [#pagination-bounds]
Paginated routes accept `cursor`, `order`, and `limit`. Cursors encode their order and canonical
snapshot, and cannot be resumed across a fork or chain. A page stops at the first bound reached:
| Bound | Maximum |
| ---------------------------------------------- | ------: |
| Rows | 1000 |
| Rows when a USD sibling is published | 200 |
| Rows when each row may require a separate mark | 100 |
| Serialized response | 4 MiB |
Responses identify the bound that stopped the page, so an exhausted dataset is distinguishable from
a page limited by size.
Numeric precision [#numeric-precision]
Exact amounts are full-width integers in base units, rendered as JSON strings. That covers token
amounts, balances, supplies, volumes, and any other value that can exceed the safe integer range of
a double. Parse them with `BigInt` or a decimal library, never `Number`:
```ts
const rawBalance = BigInt(holder.balance);
```
A numeric string is not always in token decimals. Each route page states the unit of a field: token
base units, quote base units, basis points, a fixed scale, or a rational pair. Values that are
approximate by construction say so on the response, as covered in
[What it stores](/docs/indexer/data).
Absence [#absence]
Null carries a reason and never means zero. [What it stores](/docs/indexer/data) covers this and the other derivation rules.
Error envelope [#error-envelope]
Errors use the same JSON shape at framework and handler boundaries:
```json
{"error":"bad_request","detail":"human-readable explanation"}
```
Common statuses are `400` malformed input (including an unknown query parameter), `401`/`403`
authentication or scope failure, `404` subject absent at the anchored block or an unknown chain,
`409` fork-invalid cursor or inadmissible quote, `422` a JSON body of the wrong shape or a query too
broad to serve as asked, `429` rate limit, and `503` unavailable or incomplete projection.
Only two of these are worth retrying. Retry a `429` after the `Retry-After` interval, and a `503`
with bounded exponential backoff and jitter. A `409 stale_cursor` means the walk must restart from page one. A `409 quote_not_admitted` is not retryable: reissue the request with an admissible quote.
Every other status needs a change to the request, the key, or its scope, and a retry loop will not
fix it.
Route groups [#route-groups]
This reference is checked against the Indexer's own route table and published contract:
115 routes, including the decoded transaction call-tree route, the live candle stream, and four
live-RPC routes.
A route registered by the service but missing from the published contract fails the check
rather than going undocumented. Volatile block numbers and metrics
samples are intentionally not copied into these pages; response schemas and guarantees are the
source of truth.
| Group | Routes | Covers |
| --------------------- | -----: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Tokens | 31 | Catalogue, search, detail, holders, transfers, swaps, traders, stats, ticks, candles, market cap, all-time high and low, liquidity, venue resolution, authority events, launches |
| Pools | 20 | Identity, swaps, ticks and bars, candles, stats, liquidity, extremes, and the V4 set keyed by manager and pool id |
| Wallets | 18 | Assets, portfolio, positions, PnL and daily PnL, trades, transfers, transactions, activity, internal, first seen, funding graph, cohorts |
| Chain | 13 | Blocks by number and hash, timestamp lookup, block transactions and logs, transaction envelope, receipt, logs, decoded logs, calldata, decoded call trees, and internal frames |
| Streams | 11 | Live topics, live candles, and multiplexed subscription |
| Screener and rankings | 3 | Filtered sorted views, plus self-describing named rankings |
| Cohorts | 4 | Cohorts for a token, plus metadata, members and evidence edges for one |
| Addresses | 3 | Summary, type, label |
| Live RPC | 4 | Node head, address code, native balance, and a live token metadata probe |
| Contracts | 1 | Block-bound runtime code observation |
| Batch | 1 | Several reads answered from one shared snapshot |
| Monitoring | 6 | Liveness, readiness, checkpoints, gaps, metrics |
Batch reads share a snapshot [#batch-reads-share-a-snapshot]
`POST /batch` answers several reads from one snapshot, so the items cannot disagree with each other. If you need a token's stats and its holder set to describe the same instant, this is the way to ask for them.
The body is a list of up to 20 requests. Each names a batchable route and its parameters, and may
carry an `id` that is echoed on the matching result (when omitted, the item's zero-based position
is used instead):
```json
{
"requests": [
{ "id": "token", "route": "token.overview", "params": { "address": "0x..." } },
{ "id": "wallet", "route": "wallet.overview", "params": { "address": "0x..." } }
]
}
```
The outer response carries the shared canonical head and hash, and each result carries its own
`status` with either a `body` or an `error`. A `200` for the batch does not mean every item
succeeded, so check each one: an unknown `route` name, a wallet item on a key without wallet scope,
or an item that no longer fits the batch's shared response budget (`413`) fails that item alone.
Problems with the body itself refuse the whole request instead: more than 20 items is a `400`, and
an unknown field inside `params` is a `422 invalid_json_body` rather than being dropped.
Live read-through [#live-read-through]
Four routes bypass the index and read the node directly, for when you need the current answer rather than the indexed one: head, whether an address is a contract right now, native balance right now, and a live ERC-20 metadata probe. Each is pinned to one observed block.
Rankings publish their formula [#rankings-publish-their-formula]
A named ranking returns its formula and every input per row, so the ordering can be recomputed rather than trusted. The catalogue of rankings is itself a route, and it is static.
The screener makes no editorial claim at all. Filters and sort key are yours, and it returns what matches.
---
# Streams
URL: https://www.solscanner.app/docs/indexer/streams
Live subscriptions to heads, blocks, transactions, logs, transfers, AMM events, creations and launches, with filters you can change without reconnecting.
Streams are the push side of the same data the read routes serve. Where a read route answers what is true at one block, a stream tells you as new records land.
Topics [#topics]
| Topic | Emits |
| -------------- | ------------------------------------------------------------------------------------------------------------------ |
| `heads` | Canonical head movement |
| `blocks` | New canonical blocks |
| `transactions` | New transactions |
| `logs` | New logs |
| `transfers` | New token transfers |
| `amm-events` | New swaps and liquidity events |
| `creations` | New contract and token creations |
| `launches` | Launch lifecycle transitions: new, graduating, graduated |
| `candles` | Live candle state per named token or pool series: a snapshot on connect, then updates and a final close per bucket |
Topics are isolated, so a slow consumer on one topic does not hold up another.
Every topic has its own single-topic route under `/stream/`. All of them except `heads` can also be
multiplexed through `/stream/subscribe`. The single-topic path is `/stream/amm-events`, but the
multiplexed `select` and `resume` parameters spell the same topic `amm_events`.
One connection, several topics [#one-connection-several-topics]
`GET /stream/subscribe` multiplexes several topics over a single connection rather than making you hold one per topic.
Filters on an open subscription can be changed in place:
```
POST /stream/subscribe/{topic}/{id}/selectors
```
That mutates the filter set of a subscription that is already open, so narrowing or widening what you receive does not cost a reconnect and does not create a gap while you reconnect.
What a stream event is, and is not [#what-a-stream-event-is-and-is-not]
Streams carry the same speculative-versus-canonical distinction as everything else, and it is the thing to get right when building on them.
A speculative event is an early observation. It has not been confirmed by execution, and the transaction behind it may revert. A confirmed event has been through receipt and root verification.
If you are driving something that must not act on an unconfirmed event, key on confirmed records. If you are driving a live view where being seconds early is worth being occasionally wrong, early observations are the reason streams exist. [The overview](/docs/indexer/overview) explains how the two relate.
Recovering after a disconnect [#recovering-after-a-disconnect]
Every durable event carries an `id:` of the form `v1..`. Record it only after
your application has durably processed the event, then use it to resume:
| Route | Resume with |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Single-topic streams | `after_sequence=` (the number at the end of the ID), or the full ID in the `Last-Event-ID` header when `after_sequence` is absent |
| `/stream/subscribe` | `resume=:`, one entry per topic. `Last-Event-ID` alone is not honoured here |
A browser `EventSource` resends the last full ID as `Last-Event-ID` on its own, so it resumes without
extra code. An ID is bound to its topic and store, so send it back only to the route that emitted
it. A `Last-Event-ID` the route cannot use (a bare number, an ID from another topic, or any header on
`/stream/subscribe`) is not an error: the stream opens live and its first frame is
`event: resume_unsupported`, saying the header was not honoured and which form to use instead.
Two topics do not replay history. The head stream is not durable, and a reconnect across an API
restart reports a gap. The candle stream has no resume cursor at all: every connection opens with a
fresh snapshot of each selected series, and naming `candles` in `resume` is a `400`.
Catch-up is bounded. A consumer that reads too slowly, or whose catch-up stopped short of the
present, receives an `event: lag` frame naming the last sequence delivered, instead of silently
losing events. A resume point older than what the store still retains is handled by how you sent
it:
* An explicit `after_sequence` or `resume` below the retention floor is refused with a `400` naming
the lowest sequence still retained.
* A `Last-Event-ID` below the floor is served live, and the first frame is an `event: lag` with
`reason: resume_cursor_below_retention_floor`, the requested sequence, and the `retentionFloor`.
It is served rather than refused because a browser `EventSource` never retries after a `4xx`.
In every case, read the gap from the indexed routes, then reopen the stream. Do not skip the gap.
A sequence gap is recorded as unhealthy until the corresponding canonical blocks and receipts have
been reconciled.
---
# Tokens
URL: https://www.solscanner.app/docs/indexer/reference/tokens
Token identity, supply, holders, transfers, swaps, statistics, candles and launch lifecycle.
Everything keyed by a token address. Price, market cap, liquidity and candle routes resolve against a *venue*, one pool chosen by the default-venue rule, and never blend quote currencies. A token trading against wrapped native and against a stablecoin has two separate series.
31 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Creator Tokens [#creator-tokens]
```http
GET /creators/{address}/tokens
```
Every token one deployer created, newest first.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Launchpads [#launchpads]
```http
GET /launchpads
```
The registered launchpad table this build decodes (29 pads, including the Robinhood tokenized-equities issuer `RobinhoodEquities`).
Launchpad Launches [#launchpad-launches]
```http
GET /launchpads/{pad}/launches
```
Launches from one launchpad across every one of its factory deployments (canonical factory plus aliases), oldest first by default (`order=desc` for newest first); ordered by chain coordinate (`order: ascending_by_block_then_index`) once the store's coordinate index is built, by launch sequence before that. Rows carry the same normalised `metadataUri` and `imageUri`/`imageProvenance` as `/tokens/{token}/launch`.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pad` | path | yes | Registered launchpad name. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
All Tokens [#all-tokens]
```http
GET /tokens
```
Paged catalogue of every token the store has observed, including creation-only candidates that were never confirmed as ERC-20.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Token [#token]
```http
GET /tokens/{address}
```
Token detail: name, symbol, decimals, exact supply, creation provenance, and an embedded stats block resolved against a default venue. Carries `launch { launchpad, state, metadataUri, imageUri, launchSequence }` from the launch-by-token join under the same snapshot, `null` when no launch resolved the token. Carries `holderCount { value, status, asOfBlock, burnSinkHolders }`: see "Holder count" below.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `/mcap`, `/ath`, and `/atl` are bounded, unpaginated, quote-native snapshots: no cursor, no limit, no order: only an optional `quote` to resolve which quote-denominated aggregate to answer. |
Token ATH [#token-ath]
```http
GET /tokens/{address}/ath
```
All-time high price, resolved across the token's pools within a bounded fanout. A quote asset (WETH) with no pool priced in the quote is answered from the counter's pools, inverted (`provenance` says so).
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `/mcap`, `/ath`, and `/atl` are bounded, unpaginated, quote-native snapshots: no cursor, no limit, no order: only an optional `quote` to resolve which quote-denominated aggregate to answer. |
Token ATL [#token-atl]
```http
GET /tokens/{address}/atl
```
All-time low, same shape as `/ath`.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `/mcap`, `/ath`, and `/atl` are bounded, unpaginated, quote-native snapshots: no cursor, no limit, no order: only an optional `quote` to resolve which quote-denominated aggregate to answer. |
Token Authority Events [#token-authority-events]
```http
GET /tokens/{address}/authority-events
```
Coordinate-ordered feed of privileged-action log shapes observed on the contract: ownership transfers, post-creation mints. Evidence only, no verdict.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Token Balance [#token-balance]
```http
GET /tokens/{address}/balance/{wallet}
```
One wallet's current balance of one token, derived from observed transfers.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `wallet` | path | yes | 0x-prefixed 20-byte hex address. |
Token Creator [#token-creator]
```http
GET /tokens/{address}/creator
```
The deployer address and the creation coordinate.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Token First Seen [#token-first-seen]
```http
GET /tokens/{address}/first-seen
```
The coordinate at which this token was first observed, and the evidence class that observed it.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Token Holders [#token-holders]
```http
GET /tokens/{address}/holders
```
A token's holders, **ranked by balance by default**. `sort=balance` (default) pages the same exact balance-descending holder index as `/top-holders` (same `(balance, wallet)` cursor: the two routes' cursors are interchangeable), descending only, burn sinks excluded, in this route's lighter row shape (no supply share or audit annotations). `sort=wallet_id` is the old walk: the complete non-zero balance set in interned wallet-id order (`asc` default, `desc` allowed), the only view that includes negative balances and burn sinks, NOT a ranking. Every page states `sort`, `order`, `orderedBy` and `orderingNote`. Carries `holderCount`. Before 2026-09-19 the route had no `sort` and always walked wallet-id order.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Token Liquidity [#token-liquidity]
```http
GET /tokens/{address}/liquidity
```
Protocol-native liquidity for one pair aggregated over a bounded set of contributing pools. V2 and concentrated liquidity remain separate. Same default quote and `409 quote_not_admitted` rule as `/ticks`.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | The quote every reserve/liquidity figure below is denominated in: named explicitly, auto-resolved when the token trades against exactly one resolved quote, or refused with a `400` on ambiguity: same resolution rule as `/ticks`/`/ohlcv`,. |
Token Market Cap [#token-market-cap]
```http
GET /tokens/{address}/mcap
```
Market capitalisation at the anchored block, from exact supply and a resolved price.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `/mcap`, `/ath`, and `/atl` are bounded, unpaginated, quote-native snapshots: no cursor, no limit, no order: only an optional `quote` to resolve which quote-denominated aggregate to answer. |
Token Market Cap Series [#token-market-cap-series]
```http
GET /tokens/{address}/mcap/series
```
Market-cap series over time.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `frame` | query | yes | `1s`, `30s`, `1m`, `1h`, or `1d` candle width. `1s`/`30s` rows exist only for a pool with durable fine-candle retention; see the response's own `fineSeriesAvailable`. |
| `from` | query | yes | Inclusive start of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. Must be `<= to`. |
| `to` | query | yes | Inclusive end of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. The requested span is capped at a fixed number of candle buckets. |
| `quote` | query | no | The market-cap series is a time series, so `frame`/`from`/`to` are required exactly like `/pools/{address}/ohlcv`; `quote`, `cursor`, `limit`, and `order` behave the same way too. |
| `cursor` | query | no | The market-cap series is a time series, so `frame`/`from`/`to` are required exactly like `/pools/{address}/ohlcv`; `quote`, `cursor`, `limit`, and `order` behave the same way too. |
| `limit` | query | no | The market-cap series is a time series, so `frame`/`from`/`to` are required exactly like `/pools/{address}/ohlcv`; `quote`, `cursor`, `limit`, and `order` behave the same way too. |
| `order` | query | no | The market-cap series is a time series, so `frame`/`from`/`to` are required exactly like `/pools/{address}/ohlcv`; `quote`, `cursor`, `limit`, and `order` behave the same way too. |
Token OHLCV [#token-ohlcv]
```http
GET /tokens/{address}/ohlcv
```
Token-grain candles for one pair, aggregated over the same bounded set of contributing pools. Empty buckets are absent. Same default quote and `409 quote_not_admitted` rule as `/ticks`. Sub-minute frames are gated PER POOL: cold pools are skipped (`fineSeriesColdPools`, `finePoolsSkipped`) and the hot subset is served (`fineSeriesAvailable: true`). Candle rows carry `volumeBase`/`volumeQuote` and `priceNumerator`/`priceDenominator` aliases shared with the pool grain.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `frame` | query | yes | `1s`, `30s`, `1m`, `1h`, or `1d` candle width. `1s`/`30s` rows exist only for a pool with durable fine-candle retention; see the response's own `fineSeriesAvailable`. |
| `from` | query | yes | Inclusive start of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. Must be `<= to`. |
| `to` | query | yes | Inclusive end of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. The requested span is capped at a fixed number of candle buckets. |
| `quote` | query | no | The token-grain OHLCV page. `frame`/`from`/`to` are required exactly like `/pools/{address}/ohlcv`'s. `quote` is the parameter that makes this route honest: a token trading against WETH and against a stablecoin has TWO candle series, and naming neither is refused with a `400` listing the candidates rather than blended into one. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Token Overview [#token-overview]
```http
GET /tokens/{address}/overview
```
First-paint composite: identity, price, market cap and window stats in one response. `poolsTruncated` is set for EITHER cut (the 2,048 mark bound or the 32-row listing, `poolsListedBound`); `poolFanoutExceeded` aliases `poolsFanoutExceeded`. When the 32-row listing cut applied under a resolved `price.quote`, `poolsNextCursor` + `poolsNextRoute` continue the list on `/tokens/{address}/pools/ranked?quote=` (same membership, same volume key, so the next page is exactly the pools after these 32); both are `null` otherwise. Top-level `holderCount { status: exact|exact_retained_list|backfilling|unknown_retained_list_saturated|token_not_indexed, count, retainedListSize, retainedListCap, asOfBlock, burnSinkHolders }` keeps its richer WS-B shape; the embedded `token.holderCount` is the uniform `{ value, status, asOfBlock, burnSinkHolders }` shape every other holder-bearing route publishes.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `/mcap`, `/ath`, and `/atl` are bounded, unpaginated, quote-native snapshots: no cursor, no limit, no order: only an optional `quote` to resolve which quote-denominated aggregate to answer. |
Token Pools Aggregate [#token-pools-aggregate]
```http
GET /tokens/{address}/pools/aggregate
```
Exact registered pools trading the token, grouped by counter-currency; V4 rows preserve manager plus `poolId`. Past 200 pools the busiest 200 by each pool's own trailing volume are grouped with `poolsTruncated: true` and the full `poolsConsidered`: no longer empty `groups`; `/tokens/{currency}/pools` paginates the complete set.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `/mcap`, `/ath`, and `/atl` are bounded, unpaginated, quote-native snapshots: no cursor, no limit, no order: only an optional `quote` to resolve which quote-denominated aggregate to answer. |
Token Pools Ranked [#token-pools-ranked]
```http
GET /tokens/{address}/pools/ranked
```
The token's markets: the same bounded membership `/overview`, `/mcap` and `/ohlcv` count (≤2,048, `membershipSource`), ranked by each pool's rolling-stats trailing-24h volume (`sort=volume_24h`, default) or by USD liquidity (`sort=liquidity`: each pool's `liquidityUsd`, unpriced pools last grouped by quote then quote-side amount), cursor-paged, descending only, `limit` 1..=100 (default 32). With `?quote=` it ranks that quote group by quote-native volume (the `/overview` listing order, so `/overview`'s `poolsNextCursor` resumes here); without it every quote group is ranked together by USD volume, pools whose quote has no USD mark last (grouped by quote, then quote volume). Each row is the `/overview` pool row plus `rank`, `quoteAsset`, `rankedVolume24hQuote` and `rankedVolume24hUsd`. Only the served page is hydrated.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed token address. |
| `sort` | query | no | `volume_24h` (default) or `liquidity` (`liquidityUsd`, unpriced pools last). A liquidity cursor never resumes a volume ranking. |
| `quote` | query | no | Rank one quote group by quote-native volume instead of ranking every group together by USD volume. |
| `cursor` | query | no | Opaque page cursor. `/overview`'s `poolsNextCursor` resumes here when `quote` matches. |
| `limit` | query | no | Rows per page, 1 to 100. Defaults to 32. |
| `order` | query | no | Only `desc` is accepted; any other value is a `400`. |
Token Stats [#token-stats]
```http
GET /tokens/{address}/stats
```
Rolling 5m/1h/4h/24h statistics: volume both sides, buy and sell counts, bounded distinct traders, exact price change. Same server-side default quote as `/traders` (`quoteSelection`); `quote_ambiguous` remains only when no quote has USD-markable volume.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `quote` | query | no | `quote` names the asset the windows are denominated in. Optional: a token whose pools all share one quote resolves it, and one that trades against several publishes the candidates rather than blending their units. |
Token Swaps [#token-swaps]
```http
GET /tokens/{address}/swaps
```
Compact quote-policy trades for this token, denormalised with side, amounts, pool and executor. This is not the complete exact pool universe. `quote.decimalsProvenance` says whether `quote.decimals` came from the pool-quote row or the strict token-metadata fallback.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Token Ticks [#token-ticks]
```http
GET /tokens/{address}/ticks
```
Per-trade ticks for one pair. Ticks are merged across a bounded set of contributing pools. Same server-side default quote (`quoteSelection`); a `?quote=` the pair-index fallback cannot admit is `409 quote_not_admitted` with `admissibleQuotes`, never an empty page. `poolsFanoutExceeded` aliases `poolFanoutExceeded`; prices carry `priceNumerator`/`priceDenominator` aliases. `ticksSource: token_index` (every pool of the quote group, `poolsSkipped: 0`) inside the index coverage floor `ticksIndexedFrom`, else `pool_merge`.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
| `quote` | query | no | `quote` behaves like every other quote-native token route: named explicitly, auto-resolved when the token trades against exactly one resolved quote, or refused with a `400` naming the candidates when several exist and the caller named none: never a blended series. |
Token Top Holders [#token-top-holders]
```http
GET /tokens/{address}/top-holders
```
The COMPLETE balance-descending holder index, cursor-paged, no cap (supersedes the old capped/approximate ranking, which now backs only `/traders`' internal candidate set). `orderedBy: balance_desc`; `nextCursor`/`truncatedBy` when a page ends. Burn sinks (`0x…dEaD`, `address(0)`) never get a row at all (excluded at write time) and are published as `burnedBalance`/`burnedPct`; each row adds `supplySharePct` against `circulatingSupplyExcludingDead` (`null` when that supply is not `valid`) and the balance-drift audit flags. Carries `holderCount`. Use this for a "top holders" table that needs supply share; `/holders` (default `sort=balance`) is the same order in a lighter row.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
Token Traders [#token-traders]
```http
GET /tokens/{address}/traders
```
Trader rows for the token over a window, with per-trader activity. A multi-quote token without `?quote=` now resolves the server-side default (`quoteSelection: auto_highest_24h_usd_volume`); `tokenIdentity`, `cohortsComplete`/`cohortsIncompleteReason` and `fundingGeneration` ride on the page.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `sort` | query | no | `sort` picks the ranked dimension (`remaining`, `bought`, or `pnl`: `last_active` is a recognized name that is always refused); `order` is that dimension's direction, exactly like every other paginated route. `quote` is REQUIRED whenever `sort` needs one, and otherwise resolves like `/overview`'s does: named explicitly, auto-resolved when exactly one quote exists, or left unresolved (with `availableQuotes` published) when several exist and the caller named none. |
| `order` | query | no | `sort` picks the ranked dimension (`remaining`, `bought`, or `pnl`: `last_active` is a recognized name that is always refused); `order` is that dimension's direction, exactly like every other paginated route. `quote` is REQUIRED whenever `sort` needs one, and otherwise resolves like `/overview`'s does: named explicitly, auto-resolved when exactly one quote exists, or left unresolved (with `availableQuotes` published) when several exist and the caller named none. |
| `quote` | query | no | `sort` picks the ranked dimension (`remaining`, `bought`, or `pnl`: `last_active` is a recognized name that is always refused); `order` is that dimension's direction, exactly like every other paginated route. `quote` is REQUIRED whenever `sort` needs one, and otherwise resolves like `/overview`'s does: named explicitly, auto-resolved when exactly one quote exists, or left unresolved (with `availableQuotes` published) when several exist and the caller named none. |
Token Trades Aggregate [#token-trades-aggregate]
```http
GET /tokens/{address}/trades/aggregate
```
Trade aggregate filterable by cohort tag, for splitting flow by wallet cluster. Same server-side default quote as `/traders` (`quoteSelection`).
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `period` | query | yes | `period` is required (`5m`, `1h`, `4h`, `24h`, anchored to canonical block coordinates). `tag` is optional: `creator`, `fresh_wallet`, `sniper`, `cohort:{decimal id}`, or `funded_by:0x..`; omitted, the aggregate covers every trade in the window. `quote` resolves like every other quote-native token route. |
| `tag` | query | no | `period` is required (`5m`, `1h`, `4h`, `24h`, anchored to canonical block coordinates). `tag` is optional: `creator`, `fresh_wallet`, `sniper`, `cohort:{decimal id}`, or `funded_by:0x..`; omitted, the aggregate covers every trade in the window. `quote` resolves like every other quote-native token route. |
| `quote` | query | no | `period` is required (`5m`, `1h`, `4h`, `24h`, anchored to canonical block coordinates). `tag` is optional: `creator`, `fresh_wallet`, `sniper`, `cohort:{decimal id}`, or `funded_by:0x..`; omitted, the aggregate covers every trade in the window. `quote` resolves like every other quote-native token route. |
Token Transfer Fee Observations [#token-transfer-fee-observations]
```http
GET /tokens/{address}/transfer-fee-observations
```
Per swap for one pair, the venue-stated token amount against the amount actually credited, over the same bounded set of contributing pools.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
| `quote` | query | no | `quote` behaves like every other quote-native token route: named explicitly, auto-resolved when the token trades against exactly one resolved quote, or refused with a `400` naming the candidates when several exist and the caller named none: never a blended series. |
Token Transfers [#token-transfers]
```http
GET /tokens/{address}/transfers
```
Exact ERC-20 transfer history with mint, burn and dead-sink flags.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Token Default Venue [#token-default-venue]
```http
GET /tokens/{address}/venue
```
Which pool the default-venue rule picks, and the values that decided it.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Token Launch [#token-launch]
```http
GET /tokens/{token}/launch
```
Launch lifecycle state for a launchpad token: curve, graduation status, venue, `metadataUri` (normalised on read: bare IPFS CIDs and `/ipfs/` paths as `ipfs://…`, inline JSON collapsed to its image or `""`), plus `imageUri`/`imageProvenance` (`launch_calldata`, `curated`, `none`). Every registered pad links its token as of 2026-09-18; historical rows relink through `rh-store-doctor backfill-launch-tokens`.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `token` | path | yes | 0x-prefixed 20-byte hex address. |
New Tokens [#new-tokens]
```http
GET /tokens/new
```
Newest-first discovery feed of tokens, ordered by the block/tx/log coordinate they were first observed at. Each row carries `symbol`/`name`/`decimals` from the strict metadata observation and `holderCount` (one memoised point read per distinct token per page).
| Parameter | In | Required | Notes |
| -------------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
| `launchpads` | query | no | Comma-separated canonical launchpad names: watch only these. `GET /launchpads` lists the accepted names. |
| `exclude_launchpads` | query | no | Comma-separated canonical launchpad names: exclude these. Naming the same launchpad in both is not an error; exclusion always wins. |
Token Search [#token-search]
```http
GET /tokens/search
```
Token search over symbol, name and address, composable with market and date filters and a caller-chosen sort.
| Parameter | In | Required | Notes |
| ---------------------------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `mode` | query | no | Optional `index_page` selects bounded text order, not a global ranking. |
| `allowScan` | query | no | Required for symbol/name searches, which rank every bounded text-index match. `address=` remains an indexed point lookup and needs no opt-in. |
| `address` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `symbol` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `symbol_prefix` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `name` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `name_prefix` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `quote` | query | no | Denominates every quote-dependent filter/sort AND selects which pair each row's live statistics display. Required whenever a market filter or sort key is used. |
| `window` | query | no | `5m`, `1h`, `4h`, or `24h`. Defaults to `24h` (`/screener` defaults to `5m`). |
| `sort` | query | no | The ordering key. Defaults to `first_seen_block`. Every other value is `/screener`'s own market vocabulary, reused verbatim. |
| `order` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `cursor` | query | no | Exactly one selector is accepted per request: `address` short-circuits to the exact answer; `symbol`/`name` are exact (already-normalized) matches; `symbol_prefix`/`name_prefix` are bounded prefix seeks. Combining selectors, or pairing `address` with `cursor`/`sort`/`order`, is refused rather than silently picking one. The market and date filter/sort fields below are exactly `/screener`'s own vocabulary (`min_total_volume_quote` through `require_distinct_exact`), reused verbatim, plus `min_first_seen_block`/ `max_first_seen_block` for the one axis this route adds. |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `min_total_volume_quote` | query | no | Inclusive lower bound on the row's `totalVolumeQuote`, exact quote-native base units as a decimal string. |
| `min_buy_volume_quote` | query | no | Inclusive lower bound on the row's `buyVolumeQuote`, exact quote-native base units as a decimal string. |
| `min_sell_volume_quote` | query | no | Inclusive lower bound on the row's `sellVolumeQuote`, exact quote-native base units as a decimal string. |
| `min_trades` | query | no | Inclusive lower bound on the row's `trades` count. |
| `min_buys` | query | no | Inclusive lower bound on the row's `buys` count. |
| `min_sells` | query | no | Inclusive lower bound on the row's `sells` count. |
| `min_distinct_traders_lower_bound` | query | no | Inclusive lower bound on the row's `distinctTraders.lowerBound`: the proven floor, not the (possibly higher) estimate. |
| `max_total_volume_quote` | query | no | Inclusive upper bound on the row's `totalVolumeQuote`, exact quote-native base units as a decimal string. |
| `max_trades` | query | no | Inclusive upper bound on the row's `trades` count. |
| `max_buys` | query | no | Inclusive upper bound on the row's `buys` count. |
| `max_sells` | query | no | Inclusive upper bound on the row's `sells` count. |
| `min_covered_blocks` | query | no | Inclusive lower bound on the row's `window.coveredBlocks`: excludes a subject whose ranked span was clipped short (a young chain, a thin history) below this many blocks. |
| `require_window_fully_covered` | query | no | When `true`, excludes any row where `window.windowFullyCovered` is `false`: the exact-only counterpart to `minCoveredBlocks`. |
| `require_distinct_exact` | query | no | When `true`, excludes any row whose distinct-trader counts are not proven exact (`distinctBuyers`/`distinctSellers`/`distinctTraders`' own `exact` field), rather than accepting a sketch estimate. |
| `min_first_seen_block` | query | no | Date filter: the token's first-seen block is at or above this. |
| `max_first_seen_block` | query | no | Date filter: the token's first-seen block is at or below this. |
---
# Pools
URL: https://www.solscanner.app/docs/indexer/reference/pools
Pool identity, swaps, ticks, candles, liquidity and rolling statistics, including Uniswap V4.
Everything keyed by a pool. V4 pools are keyed by manager address plus pool id rather than a pool address, so they have their own parallel route set. Liquidity is protocol-native: V2 reserves and concentrated-liquidity depth are different measurements and are not merged.
20 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Pools By Currency [#pools-by-currency]
```http
GET /currencies/{currency}/pools
```
Pools holding a given currency on either side.
| Parameter | In | Required | Notes |
| ---------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `currency` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Pool [#pool]
```http
GET /pools/{address}
```
Pool identity: protocol, both currencies, fee and tick spacing, factory coordinate, embedded stats.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Pool ATH [#pool-ath]
```http
GET /pools/{address}/ath
```
All-time high price for the pool.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Pool ATL [#pool-atl]
```http
GET /pools/{address}/atl
```
All-time low, same shape.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Pool Liquidity [#pool-liquidity]
```http
GET /pools/{address}/liquidity
```
Exact protocol-native liquidity: V2 reserves, or V3 active depth at the current tick; plus `liquidityUsd` (V2 reserves, or V3 current-tick-spacing range amounts, each side through its own USD mark; null with a reason when a side is unpriced) and `usdMarks`.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Pool OHLCV [#pool-ohlcv]
```http
GET /pools/{address}/ohlcv
```
1m/1h/1d candles with two-sided volume and rational OHLC.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `frame` | query | yes | `1s`, `30s`, `1m`, `1h`, or `1d` candle width. `1s`/`30s` rows exist only for a pool with durable fine-candle retention; see the response's own `fineSeriesAvailable`. |
| `from` | query | yes | Inclusive start of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. Must be `<= to`. |
| `to` | query | yes | Inclusive end of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. The requested span is capped at a fixed number of candle buckets. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Pool Stats [#pool-stats]
```http
GET /pools/{address}/stats
```
Rolling 5m/1h/4h/24h statistics for the pool.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Pool Swaps [#pool-swaps]
```http
GET /pools/{address}/swaps
```
Exact swaps for the pool with full block, transaction and log coordinates.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Pool Ticks [#pool-ticks]
```http
GET /pools/{address}/ticks
```
Per-trade tick rows enriched with transaction context.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Pool Bars [#pool-bars]
```http
GET /pools/{address}/ticks/bars
```
N-trade or N-volume bars folded over the tick sequence.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `rule` | query | yes | `rule` is exactly `trade` or `volume`; `size` is its decimal-string threshold: a plain trade count for `rule=trade`, an exact base-unit quote quantity for `rule=volume` (large enough that a plain integer query type would not always hold it). The cursor is the same tick coordinate the swap and tick routes use, since a bar page resumes the identical underlying walk. |
| `size` | query | yes | `rule` is exactly `trade` or `volume`; `size` is its decimal-string threshold: a plain trade count for `rule=trade`, an exact base-unit quote quantity for `rule=volume` (large enough that a plain integer query type would not always hold it). The cursor is the same tick coordinate the swap and tick routes use, since a bar page resumes the identical underlying walk. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
New Pools [#new-pools]
```http
GET /pools/new
```
Newest-first feed of registered pools, by creation coordinate.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Pool Search [#pool-search]
```http
GET /pools/search
```
Bounded, filtered pool discovery anchored on one currency, in pool-identity order by default (`sort=pool_identity`). `sort=volume_24h` (requires `token`, descending only) ranks the token's markets by trailing-24h volume exactly as `/tokens/{address}/pools/ranked` does (quote-native under `quote`, else USD with unpriced pools last), with the same refinement filters and `allowScan` rule; rows add `rank`, `quoteAsset`, `rankedVolume24hQuote`, `rankedVolume24hUsd`, and the page adds `rankingBasis`, `membership` and `usdMarks`. `sort=liquidity` is refused (`400`); the USD-liquidity ranking is `/tokens/{address}/pools/ranked?sort=liquidity`.
| Parameter | In | Required | Notes |
| -------------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowScan` | query | no | Required only when a refinement filter (`fee_tier`, `protocol`, `created_from`, `created_to`) makes the indexed walk examine rows that are not returned. An unfiltered anchor-currency page and an unfiltered `token`+`quote` pair page are both indexed. |
| `token` | query | no | `token`/`quote` name the pool's two currencies (either or both may be given; at least one is required as the anchor currency the walk is indexed on). `fee_tier`/`protocol`/`created_from`/`created_to` refine that walk. `factory` is not accepted; see its row below. |
| `quote` | query | no | `token`/`quote` name the pool's two currencies (either or both may be given; at least one is required as the anchor currency the walk is indexed on). `fee_tier`/`protocol`/`created_from`/`created_to` refine that walk. `factory` is not accepted; see its row below. |
| `fee_tier` | query | no | `token`/`quote` name the pool's two currencies (either or both may be given; at least one is required as the anchor currency the walk is indexed on). `fee_tier`/`protocol`/`created_from`/`created_to` refine that walk. `factory` is not accepted; see its row below. |
| `protocol` | query | no | `token`/`quote` name the pool's two currencies (either or both may be given; at least one is required as the anchor currency the walk is indexed on). `fee_tier`/`protocol`/`created_from`/`created_to` refine that walk. `factory` is not accepted; see its row below. |
| `factory` | query | no | Always rejected with a `400`: this store does not retain which factory emitted a pool's identity, so there is no value to filter or report on. The field exists only so the rejection is specific rather than a generic "unknown field" error. Supported filters: `token`, `quote`, `fee_tier`, `protocol`, `created_from`, `created_to`. |
| `created_from` | query | no | `token`/`quote` name the pool's two currencies (either or both may be given; at least one is required as the anchor currency the walk is indexed on). `fee_tier`/`protocol`/`created_from`/`created_to` refine that walk. `factory` is not accepted; see its row below. |
| `created_to` | query | no | `token`/`quote` name the pool's two currencies (either or both may be given; at least one is required as the anchor currency the walk is indexed on). `fee_tier`/`protocol`/`created_from`/`created_to` refine that walk. `factory` is not accepted; see its row below. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
V4 Pool [#v4-pool]
```http
GET /pools/v4/{manager}/{pool_id}
```
V4 pool identity, keyed by manager address and pool id rather than a pool address.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
V4 Pool Liquidity [#v4-pool-liquidity]
```http
GET /pools/v4/{manager}/{pool_id}/liquidity
```
V4 liquidity observation, plus `liquidityUsd` as for V3.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
V4 Pool OHLCV [#v4-pool-ohlcv]
```http
GET /pools/v4/{manager}/{pool_id}/ohlcv
```
V4 candles.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
| `frame` | query | yes | `1s`, `30s`, `1m`, `1h`, or `1d` candle width. `1s`/`30s` rows exist only for a pool with durable fine-candle retention; see the response's own `fineSeriesAvailable`. |
| `from` | query | yes | Inclusive start of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. Must be `<= to`. |
| `to` | query | yes | Inclusive end of the unix-second range the candle series covers, aligned down to its `frame`'s own bucket width. The requested span is capped at a fixed number of candle buckets. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
V4 Pool Stats [#v4-pool-stats]
```http
GET /pools/v4/{manager}/{pool_id}/stats
```
V4 rolling-window statistics.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
V4 Pool Swaps [#v4-pool-swaps]
```http
GET /pools/v4/{manager}/{pool_id}/swaps
```
V4 swaps.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
V4 Pool Ticks [#v4-pool-ticks]
```http
GET /pools/v4/{manager}/{pool_id}/ticks
```
V4 per-trade ticks.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
V4 Pool Bars [#v4-pool-bars]
```http
GET /pools/v4/{manager}/{pool_id}/ticks/bars
```
V4 trade or volume bars.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `manager` | path | yes | The V4 pool manager contract. Many distinct pools share one manager address: this alone does not identify a pool, pair it with `pool_id` below. Obtain both together from a real pool's `contract`/`poolId` fields via `GET /pools/search` or `GET /pools/new` (filter or scan for `"protocol":"v4"`); a V4 manager address cannot be looked up through the plain `GET /pools/{address}` route. |
| `pool_id` | path | yes | Stored V4 pool identity key, hex. Obtain a real one from `pools/search` or `pools/new`'s `poolId` field, paired with the same pool's `contract` as `manager` above: V4 pools cannot be looked up via the plain `/pools/{address}` route. |
| `rule` | query | yes | `rule` is exactly `trade` or `volume`; `size` is its decimal-string threshold: a plain trade count for `rule=trade`, an exact base-unit quote quantity for `rule=volume` (large enough that a plain integer query type would not always hold it). The cursor is the same tick coordinate the swap and tick routes use, since a bar page resumes the identical underlying walk. |
| `size` | query | yes | `rule` is exactly `trade` or `volume`; `size` is its decimal-string threshold: a plain trade count for `rule=trade`, an exact base-unit quote quantity for `rule=volume` (large enough that a plain integer query type would not always hold it). The cursor is the same tick coordinate the swap and tick routes use, since a bar page resumes the identical underlying walk. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Pools By Token [#pools-by-token]
```http
GET /tokens/{currency}/pools
```
Pools where this address is one of the two currencies, in pool-identity order (not a ranking; includes pools where the address is the QUOTE). For a volume-ranked list of the token's own markets use `/pools/ranked`.
| Parameter | In | Required | Notes |
| ---------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `currency` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
---
# Wallets
URL: https://www.solscanner.app/docs/indexer/reference/wallets
Holdings, portfolio, positions and PnL, trades, transfers, transactions, activity and the funding graph.
Everything keyed by a wallet address. Balances are the running signed sum of decoded transfers rather than `balanceOf` calls, so a holdings row exists only while the balance is non-zero. Routes whose coverage is partial say so on every response.
18 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Wallet Activity [#wallet-activity]
```http
GET /wallets/{address}/activity
```
Unified feed merging transactions, transfers and swaps into parent entries with typed child events.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Assets [#wallet-assets]
```http
GET /wallets/{address}/assets
```
What the wallet currently holds. A row exists only while the balance is non-zero, so this is holdings, not ever-held. `sort=token` (default) walks interned token-id order (`asc`/`desc`). `sort=usd` ranks by `balanceUsd.value` descending with unpriced rows last (token-id order, reason on `balanceUsd.reason`); `sort=balance` ranks by raw smallest-unit balance descending (a quantity order across different decimals, not a value order). Both ranked sorts are descending only, read at most `sortBudget` (500) assets, and set `sortTruncated: true` when the wallet holds more: the ranking then covers the first 500 by token id and says so. Ranked paging uses a `(class, key, tokenId)` cursor and is exact within one ranking. `limit` 1..=100.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Cohorts [#wallet-cohorts]
```http
GET /wallets/{address}/cohorts
```
Cohorts this wallet belongs to. `Funding` memberships a current entity override screens out are dropped and listed in `screened`/`screenedOut`.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
Wallet First Seen [#wallet-first-seen]
```http
GET /wallets/{address}/first-seen
```
When the wallet was first observed.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Wallet Funded By [#wallet-funded-by]
```http
GET /wallets/{address}/funded-by
```
Earliest incoming value transfer per asset, and who sent it.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Funded By All [#wallet-funded-by-all]
```http
GET /wallets/{address}/funded-by-all
```
Every funder edge rather than only the earliest.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Wallet Funded Siblings [#wallet-funded-siblings]
```http
GET /wallets/{address}/funded-siblings
```
Wallets sharing a funder with this one.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Wallet Funder Of [#wallet-funder-of]
```http
GET /wallets/{address}/funder-of
```
The reverse edge: everything this wallet funded.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Internal Transfers [#wallet-internal-transfers]
```http
GET /wallets/{address}/internal
```
Internal value transfers and contract creations the address took part in.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Overview [#wallet-overview]
```http
GET /wallets/{address}/overview
```
First-paint composite for a wallet. Bounded aggregate, not a page. `cohortsComplete`/`cohortsIncompleteReason`/`fundingGeneration` qualify the embedded cohort list.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Wallet PnL [#wallet-pnl]
```http
GET /wallets/{address}/pnl
```
Bounded PnL aggregate that publishes its own scan bounds and completeness rather than paginating.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Wallet Token PnL [#wallet-token-pnl]
```http
GET /wallets/{address}/pnl/{token}
```
PnL detail for a single token.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `token` | path | yes | 0x-prefixed 20-byte hex address. |
Wallet PnL Daily [#wallet-pnl-daily]
```http
GET /wallets/{address}/pnl/daily
```
Daily realised series for one `(token, quote)` pair.
| Parameter | In | Required | Notes |
| ---------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `token` | query | yes | `token` and `quote` are required. A daily series is always scoped to one `(token, quote)` pair; a wallet-wide series across every token is not served by this route. |
| `quote` | query | yes | `token` and `quote` are required. A daily series is always scoped to one `(token, quote)` pair; a wallet-wide series across every token is not served by this route. |
| `from_day` | query | no | Inclusive lower bound on `day` (a UTC day index, not a timestamp: see the response's own `day`/`dayStartUnix`). Defaults to no floor. |
| `to_day` | query | no | Inclusive upper bound on `day`. Defaults to no ceiling. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Portfolio [#wallet-portfolio]
```http
GET /wallets/{address}/portfolio
```
Net worth with coverage: what could be marked, what could not and why, and the basis split. Inclusion predicates are caller-chosen.
| Parameter | In | Required | Notes |
| ----------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `predicate` | query | no | Which published inclusion predicate produced the total: `all` (default), `liquid`, `verified_contracts`, or `no_privileged_action_observed`. An unrecognized name is refused rather than defaulted. |
Wallet Positions [#wallet-positions]
```http
GET /wallets/{address}/positions
```
Per-token WAC positions with realised and unrealised components, paged by token.
| Parameter | In | Required | Notes |
| --------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
| `hide_small` | query | no | Decimal token-smallest-unit threshold; a non-negative row below it is dropped. |
| `sellout` | query | no | Optional holdings filters. `hide_small` drops rows below the given smallest-unit threshold; `sellout` and `hide_abnormal` drop fully exited and anomalous rows. Filters never change paging: the cursor walks the same underlying set either way. |
| `hide_abnormal` | query | no | Optional holdings filters. `hide_small` drops rows below the given smallest-unit threshold; `sellout` and `hide_abnormal` drop fully exited and anomalous rows. Filters never change paging: the cursor walks the same underlying set either way. |
| `sort` | query | no | Accepted as a name but always refused: this route's ordering is fixed and cannot be re-sorted. |
Wallet Trades [#wallet-trades]
```http
GET /wallets/{address}/trades
```
Wallet swap history. The wallet is the transaction origin, never the router recipient. `quote.decimals` falls back to the strict token-metadata observation when the pool-quote row never resolved it; `quote.decimalsProvenance` names the source.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Transactions [#wallet-transactions]
```http
GET /wallets/{address}/transactions
```
Top-level transactions where the wallet is origin or recipient, merged and deduplicated.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Wallet Transfers [#wallet-transfers]
```http
GET /wallets/{address}/transfers
```
Direction-aware transfer history. Native rows carry a reserved asset kind rather than a zero address.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
---
# Screener and rankings
URL: https://www.solscanner.app/docs/indexer/reference/screener
Filtered and sorted views over tokens and pools, and named rankings that publish their own formula.
The screener makes no editorial claim: filters and sort key are yours and it returns what matches. Each named ranking returns its formula and every input per row, so the order can be recomputed rather than trusted.
3 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Rankings Catalog [#rankings-catalog]
```http
GET /rankings
```
Self-describing catalogue of the named rankings, their formulas and parameters. Static, no store read.
Rankings [#rankings]
```http
GET /rankings/{preset}
```
One named ranking. Publishes its formula and every input per row so the order can be recomputed.
| Parameter | In | Required | Notes |
| --------------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preset` | path | yes | - |
| `allowScan` | query | no | Required acknowledgement for the preset's bounded enumerator scan. |
| `grain` | query | no | `pool` or `token_pair`. Defaults to `token_pair`. |
| `quote` | query | yes | REQUIRED. The asset every ranked volume is denominated in. |
| `base` | query | no | Restrict the ranking to one base asset. |
| `window` | query | no | `5m`, `1h`, `4h`, or `24h`. Defaults to `5m`. |
| `metric` | query | no | The window figure `largest` orders on. Refused for every other preset, which have formulas rather than metrics. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `recency_floor_block` | query | no | Narrows the enumerator scan to subjects that last traded at or after this block, and is republished in `equivalentScreenerQuery`. |
Screener [#screener]
```http
GET /screener
```
Filtered, sorted page over tokens and pools. Filters and sort key are caller-chosen; the response makes no editorial claim. `base`/`quote` carry `symbol`/`name`; every row carries `holderCount { value, status, asOfBlock, burnSinkHolders }` (one point read per distinct base). `sort=holders` ranks by `holderCount.value` descending (exact counts first) WITHIN a labelled candidate set: the top 100 subjects by `total_volume_quote` under the request's filters, because holder counts are not in the rolling ring the scan can prefilter on; `holdersRanking` names that set, `rank` is `null` and `rankWithinPrefilterSet` is the position within it. The candidate-set `503` carries `suggestedRecencyFloorBlock`, `candidatesScanned`, `ceiling`, `rankedAtBlock` as fields.
| Parameter | In | Required | Notes |
| ---------------------------------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowScan` | query | no | Required acknowledgement for this route's bounded enumerator scan. |
| `grain` | query | no | `pool` or `token_pair`. Defaults to `token_pair`. |
| `quote` | query | yes | REQUIRED. The asset every ranked volume is denominated in. |
| `base` | query | no | Restrict the ranking to one base asset. |
| `window` | query | no | `5m`, `1h`, `4h`, or `24h`. Defaults to `5m`. |
| `sort` | query | no | The ordering key. Defaults to `total_volume_quote`. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `recency_floor_block` | query | no | Narrows the enumerator scan to subjects that last traded at or after this block. Bound-safe, and the response republishes it. |
| `min_total_volume_quote` | query | no | Inclusive lower bound on the row's `totalVolumeQuote`, exact quote-native base units as a decimal string. |
| `min_buy_volume_quote` | query | no | Inclusive lower bound on the row's `buyVolumeQuote`, exact quote-native base units as a decimal string. |
| `min_sell_volume_quote` | query | no | Inclusive lower bound on the row's `sellVolumeQuote`, exact quote-native base units as a decimal string. |
| `min_trades` | query | no | Inclusive lower bound on the row's `trades` count. |
| `min_buys` | query | no | Inclusive lower bound on the row's `buys` count. |
| `min_sells` | query | no | Inclusive lower bound on the row's `sells` count. |
| `min_distinct_traders_lower_bound` | query | no | Inclusive lower bound on the row's `distinctTraders.lowerBound`: the proven floor, not the (possibly higher) estimate. |
| `max_total_volume_quote` | query | no | Inclusive upper bound on the row's `totalVolumeQuote`, exact quote-native base units as a decimal string. |
| `max_trades` | query | no | Inclusive upper bound on the row's `trades` count. |
| `max_buys` | query | no | Inclusive upper bound on the row's `buys` count. |
| `max_sells` | query | no | Inclusive upper bound on the row's `sells` count. |
| `min_covered_blocks` | query | no | Inclusive lower bound on the row's `window.coveredBlocks`: excludes a subject whose ranked span was clipped short (a young chain, a thin history) below this many blocks. |
| `require_window_fully_covered` | query | no | When `true`, excludes any row where `window.windowFullyCovered` is `false`: the exact-only counterpart to `minCoveredBlocks`. |
| `require_distinct_exact` | query | no | When `true`, excludes any row whose distinct-trader counts are not proven exact (`distinctBuyers`/`distinctSellers`/`distinctTraders`' own `exact` field), rather than accepting a sketch estimate. |
---
# Cohorts
URL: https://www.solscanner.app/docs/indexer/reference/cohorts
Wallet clusters, their members, and the evidence edges that produced them.
A cohort is a set of wallets the Indexer has grouped together. Each cohort publishes the edges that produced it, so a membership claim can be inspected rather than taken on faith.
4 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Cohorts By Token [#cohorts-by-token]
```http
GET /cohorts
```
Funding cohorts seeded by members funded in this token, in ascending cohort id. `primary` marks the anchor asset. The cursor is bound to the funding generation, so a new generation answers `409 stale_cursor` and the walk restarts.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ---------------------------------------------------- |
| `token` | query | yes | 0x-prefixed token address to look up cohorts for. |
| `cursor` | query | no | Opaque page cursor, bound to the funding generation. |
| `limit` | query | no | Rows per page. |
Cohort Detail [#cohort-detail]
```http
GET /cohorts/{id}
```
Cohort metadata: kind, size, primary asset, `generation`, and for a `Funding` cohort `freshness {ruleVersion, overridesVersion, currentRuleVersion, currentOverridesVersion, stale, staleReasons}` plus a hoisted `stale`.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ------------------------------------------------ |
| `id` | path | yes | Cohort id: 8 bytes, 16 hex digits, no 0x prefix. |
Cohort Edges [#cohort-edges]
```http
GET /cohorts/{id}/edges
```
The evidence edges that produced membership. Screened members' edges are withheld (`screenedOut`), with the same `freshness` and `cohortsComplete` trio as `/members`.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | path | yes | Cohort id: 8 bytes, 16 hex digits, no 0x prefix. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
Cohort Members [#cohort-members]
```http
GET /cohorts/{id}/members
```
Member wallets, each with `admittedBy {signals, edge, funder, assetKind, asset, firstBlock, lastBlock}`. Members a current entity override screens out (the member, or the funder that admitted it) are dropped and listed in `screened`/`screenedOut`, and the page reports `freshness.stale`. `cohortsComplete`/`cohortsIncompleteReason`/`fundingGeneration` state whether the funding half was ever derived.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | path | yes | Cohort id: 8 bytes, 16 hex digits, no 0x prefix. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
---
# Blocks and transactions
URL: https://www.solscanner.app/docs/indexer/reference/chain
Blocks, transactions, receipts, logs, decoded logs and calldata, and internal transfers.
The raw substrate. Transaction envelopes and receipts are byte-exact canonical records. Decoded views resolve where a decoder exists and report the rest as unknown rather than omitting them. Internal transfers come from node re-execution and are the one data class not verifiable against a block root.
13 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Block [#block]
```http
GET /blocks/{number}
```
Canonical block header.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------- |
| `number` | path | yes | Canonical block number. |
Block Logs [#block-logs]
```http
GET /blocks/{number}/logs
```
Logs emitted in a block.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `number` | path | yes | Canonical block number. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Block Logs Decoded [#block-logs-decoded]
```http
GET /blocks/{number}/logs/decoded
```
The same logs, decoded where a decoder resolves. Unresolved logs are reported as unknown.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `number` | path | yes | Canonical block number. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Block Transactions [#block-transactions]
```http
GET /blocks/{number}/transactions
```
Transactions in a block.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `number` | path | yes | Canonical block number. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Block At [#block-at]
```http
GET /blocks/at
```
Timestamp-to-block: both named bounds of a timestamp against one snapshot.
| Parameter | In | Required | Notes |
| ----------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `timestamp` | query | yes | `timestamp` is required and is Unix seconds; there is no default, because defaulting an instant would silently answer a different question. |
Block By Hash [#block-by-hash]
```http
GET /blocks/by-hash/{hash}
```
Block header by hash.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
Transaction [#transaction]
```http
GET /transactions/{hash}
```
Transaction envelope, byte-exact. Plus `from`/`to`/`value` from the transaction-origin sidecar (`from: null` with `fromReason: coverage_unavailable | outside_coverage | unrecoverable` when it cannot answer) and `originCoverage`.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
Transaction Call Tree [#transaction-call-tree]
```http
GET /transactions/{hash}/calls/decoded
```
The transaction's decoded call tree: ordered frames carrying ordinal, parent, depth, kind, from, to, value, decoded calldata, output, and revert state. Frames are the tracer's actual frames, not inferred from hooks, and a delegate frame's value is the tracer quantity rather than a claim that value moved.
Coverage is explicit: a transaction with no retained call tree answers `coverage: not_available` with a reason rather than an empty frame list. Like every trace-derived route, this is built from node re-execution and is not verifiable against a block root.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ----------------------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte transaction hash. |
| `cursor` | query | no | Opaque page cursor over call frames. |
| `limit` | query | no | Frames per page, 1 to 64. Defaults to 32. |
Transaction Decoded [#transaction-decoded]
```http
GET /transactions/{hash}/decoded
```
Decoded calldata for the transaction, from hand-written decoders and verified-ABI bindings.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
Transaction Internal Transfers [#transaction-internal-transfers]
```http
GET /transactions/{hash}/internal
```
Retained internal value transfers and contract creations, with per-transaction retention and reconciliation state.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Transaction Logs [#transaction-logs]
```http
GET /transactions/{hash}/logs
```
Logs for one transaction.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Transaction Logs Decoded [#transaction-logs-decoded]
```http
GET /transactions/{hash}/logs/decoded
```
Decoded logs for one transaction.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Receipt [#receipt]
```http
GET /transactions/{hash}/receipt
```
Receipt including chain-specific L1 gas accounting.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | ----------------------------- |
| `hash` | path | yes | 0x-prefixed 32-byte hex hash. |
---
# Stream routes
URL: https://www.solscanner.app/docs/indexer/reference/streams
Live subscriptions to heads, blocks, transactions, logs, transfers, AMM events, creations, launches and live candles.
Push access to the same records the read routes serve. Prefer the multiplexed subscription over one connection per entity: each key may hold only a handful of open streams at once (eight by default), and one stream too many answers `429` without a `Retry-After`. One connection counts once however many topics and selectors it carries, so a client watching fifty tokens puts them on one subscription rather than fifty connections. Durable IDs are topic- and store-generation-bound; idle streams send keep-alive comments, and lagging consumers receive an explicit lag event before backfilling through REST. See [Streams](/docs/indexer/streams) for the concepts.
11 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
AMM Events [#amm-events]
```http
GET /stream/amm-events
```
New swaps and liquidity events; each `swap` frame carries `sender`, `origin` (the transaction sender, `null` outside tx-origin coverage) and `transactionHash`.
| Parameter | In | Required | Notes |
| ------------------- | ----- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tokens` | query | no | Query parameters selecting one subscription's bounded watchlist. `tokens`, `pools`, `wallets`, and `launchpads` are comma-separated. A pool entry is either `0xcontract` (V2/V3, or V4 with an implicit zero `PoolId`) or `0xmanager:0xpoolid` (V4). A launchpad entry is its canonical, config-facing name (e.g. `trench`, `flap`): matching a swap only when that swap's pool contract is the launchpad's own shared curve/portal address. `all=true` subscribes to every event of this topic regardless of any other selector, the "all" bucket, and alone satisfies the "at least one selector" requirement. `min_leg_magnitude` is the one permitted non-invertible filter: a decimal-string floor on `swap_magnitude`, the larger of the swap's two raw pool-side deltas, in that leg's own base units, applied only after a selector already matched. This is deliberately NOT quote-native and NOT decimal-scaled: it does not resolve which leg is the quote asset or how many decimals either token uses, so the same raw value means a wildly different real-world size depending on which leg happens to be larger and that leg's decimals. `after_sequence` resumes a reconnecting consumer from its last delivered resume cursor, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. When it is absent, the request's `Last-Event-ID` header supplies it instead, which is how a browser's native `EventSource` resumes, since it can only reconnect to the URL it was built with. An explicit `after_sequence` always wins. |
| `pools` | query | no | Query parameters selecting one subscription's bounded watchlist. `tokens`, `pools`, `wallets`, and `launchpads` are comma-separated. A pool entry is either `0xcontract` (V2/V3, or V4 with an implicit zero `PoolId`) or `0xmanager:0xpoolid` (V4). A launchpad entry is its canonical, config-facing name (e.g. `trench`, `flap`): matching a swap only when that swap's pool contract is the launchpad's own shared curve/portal address. `all=true` subscribes to every event of this topic regardless of any other selector, the "all" bucket, and alone satisfies the "at least one selector" requirement. `min_leg_magnitude` is the one permitted non-invertible filter: a decimal-string floor on `swap_magnitude`, the larger of the swap's two raw pool-side deltas, in that leg's own base units, applied only after a selector already matched. This is deliberately NOT quote-native and NOT decimal-scaled: it does not resolve which leg is the quote asset or how many decimals either token uses, so the same raw value means a wildly different real-world size depending on which leg happens to be larger and that leg's decimals. `after_sequence` resumes a reconnecting consumer from its last delivered resume cursor, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. When it is absent, the request's `Last-Event-ID` header supplies it instead, which is how a browser's native `EventSource` resumes, since it can only reconnect to the URL it was built with. An explicit `after_sequence` always wins. |
| `wallets` | query | no | Query parameters selecting one subscription's bounded watchlist. `tokens`, `pools`, `wallets`, and `launchpads` are comma-separated. A pool entry is either `0xcontract` (V2/V3, or V4 with an implicit zero `PoolId`) or `0xmanager:0xpoolid` (V4). A launchpad entry is its canonical, config-facing name (e.g. `trench`, `flap`): matching a swap only when that swap's pool contract is the launchpad's own shared curve/portal address. `all=true` subscribes to every event of this topic regardless of any other selector, the "all" bucket, and alone satisfies the "at least one selector" requirement. `min_leg_magnitude` is the one permitted non-invertible filter: a decimal-string floor on `swap_magnitude`, the larger of the swap's two raw pool-side deltas, in that leg's own base units, applied only after a selector already matched. This is deliberately NOT quote-native and NOT decimal-scaled: it does not resolve which leg is the quote asset or how many decimals either token uses, so the same raw value means a wildly different real-world size depending on which leg happens to be larger and that leg's decimals. `after_sequence` resumes a reconnecting consumer from its last delivered resume cursor, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. When it is absent, the request's `Last-Event-ID` header supplies it instead, which is how a browser's native `EventSource` resumes, since it can only reconnect to the URL it was built with. An explicit `after_sequence` always wins. |
| `launchpads` | query | no | Query parameters selecting one subscription's bounded watchlist. `tokens`, `pools`, `wallets`, and `launchpads` are comma-separated. A pool entry is either `0xcontract` (V2/V3, or V4 with an implicit zero `PoolId`) or `0xmanager:0xpoolid` (V4). A launchpad entry is its canonical, config-facing name (e.g. `trench`, `flap`): matching a swap only when that swap's pool contract is the launchpad's own shared curve/portal address. `all=true` subscribes to every event of this topic regardless of any other selector, the "all" bucket, and alone satisfies the "at least one selector" requirement. `min_leg_magnitude` is the one permitted non-invertible filter: a decimal-string floor on `swap_magnitude`, the larger of the swap's two raw pool-side deltas, in that leg's own base units, applied only after a selector already matched. This is deliberately NOT quote-native and NOT decimal-scaled: it does not resolve which leg is the quote asset or how many decimals either token uses, so the same raw value means a wildly different real-world size depending on which leg happens to be larger and that leg's decimals. `after_sequence` resumes a reconnecting consumer from its last delivered resume cursor, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. When it is absent, the request's `Last-Event-ID` header supplies it instead, which is how a browser's native `EventSource` resumes, since it can only reconnect to the URL it was built with. An explicit `after_sequence` always wins. |
| `all` | query | no | `all=true` subscribes to every event of this topic regardless of any other selector: the "all" bucket, and alone satisfies this route's "at least one selector" requirement. |
| `min_leg_magnitude` | query | no | Query parameters selecting one subscription's bounded watchlist. `tokens`, `pools`, `wallets`, and `launchpads` are comma-separated. A pool entry is either `0xcontract` (V2/V3, or V4 with an implicit zero `PoolId`) or `0xmanager:0xpoolid` (V4). A launchpad entry is its canonical, config-facing name (e.g. `trench`, `flap`): matching a swap only when that swap's pool contract is the launchpad's own shared curve/portal address. `all=true` subscribes to every event of this topic regardless of any other selector, the "all" bucket, and alone satisfies the "at least one selector" requirement. `min_leg_magnitude` is the one permitted non-invertible filter: a decimal-string floor on `swap_magnitude`, the larger of the swap's two raw pool-side deltas, in that leg's own base units, applied only after a selector already matched. This is deliberately NOT quote-native and NOT decimal-scaled: it does not resolve which leg is the quote asset or how many decimals either token uses, so the same raw value means a wildly different real-world size depending on which leg happens to be larger and that leg's decimals. `after_sequence` resumes a reconnecting consumer from its last delivered resume cursor, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. When it is absent, the request's `Last-Event-ID` header supplies it instead, which is how a browser's native `EventSource` resumes, since it can only reconnect to the URL it was built with. An explicit `after_sequence` always wins. |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
Blocks [#blocks]
```http
GET /stream/blocks
```
New canonical blocks.
| Parameter | In | Required | Notes |
| ---------------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
Stream Candles [#stream-candles]
```http
GET /stream/candles
```
Live candles per named series (`select=token:0x..:1m[:0xquote]`, `pool:0x..:1h`): a `snapshot` of the previous and forming bucket on connect, then `update` frames (at most one per 250 ms per series) and a `close` frame (`final: true`) when the head leaves the bucket. `candle` is the REST compact row. No resume cursor.
The `candles` topic of `/stream/subscribe` takes the same selectors, including selector mutation.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `select` | query | yes | Comma-separated series: `token:0x:[:0x]` or `pool::`, where `` is a pool address or V4 `0x:0x` and `` is any frame the REST candle routes serve. |
Creations [#creations]
```http
GET /stream/creations
```
New contract and token creations.
| Parameter | In | Required | Notes |
| ---------------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contracts` | query | no | Query parameters selecting one creations subscription's bounded watchlist: `contracts` (the created address) and `wallets` (the recovered creator, when known). |
| `wallets` | query | no | Query parameters selecting one creations subscription's bounded watchlist: `contracts` (the created address) and `wallets` (the recovered creator, when known). |
| `all` | query | no | `all=true` subscribes to every event of this topic regardless of any other selector: the "all" bucket, and alone satisfies this route's "at least one selector" requirement. |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
Heads [#heads]
```http
GET /stream/heads
```
Canonical head movement.
Launches [#launches]
```http
GET /stream/launches
```
Launch lifecycle transitions (`launch_new` frames carry the normalised `metadataUri` plus `imageUri`/`imageProvenance`): new, graduating, graduated.
| Parameter | In | Required | Notes |
| -------------------- | ----- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tokens` | query | no | Query parameters selecting one launch-lifecycle subscription's bounded watchlist: `tokens` and `launchpads` (watch exactly these launchpads). `exclude_launchpads` is the other direction: everything EXCEPT these launchpads, a bounded post-match bitset rather than an inverted-index key. Naming the same launchpad in both is not an error: exclusion always wins, since it is checked after any key match, include or otherwise. |
| `launchpads` | query | no | Query parameters selecting one launch-lifecycle subscription's bounded watchlist: `tokens` and `launchpads` (watch exactly these launchpads). `exclude_launchpads` is the other direction: everything EXCEPT these launchpads, a bounded post-match bitset rather than an inverted-index key. Naming the same launchpad in both is not an error: exclusion always wins, since it is checked after any key match, include or otherwise. |
| `exclude_launchpads` | query | no | Query parameters selecting one launch-lifecycle subscription's bounded watchlist: `tokens` and `launchpads` (watch exactly these launchpads). `exclude_launchpads` is the other direction: everything EXCEPT these launchpads, a bounded post-match bitset rather than an inverted-index key. Naming the same launchpad in both is not an error: exclusion always wins, since it is checked after any key match, include or otherwise. |
| `all` | query | no | `all=true` subscribes to every event of this topic regardless of any other selector: the "all" bucket, and alone satisfies this route's "at least one selector" requirement. |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
Logs [#logs]
```http
GET /stream/logs
```
New logs.
| Parameter | In | Required | Notes |
| ---------------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addresses` | query | no | Query parameters selecting one logs subscription's bounded watchlist: `addresses` (emitting contract) and `topics` (`topic0`, the event signature). |
| `topics` | query | no | Query parameters selecting one logs subscription's bounded watchlist: `addresses` (emitting contract) and `topics` (`topic0`, the event signature). |
| `all` | query | no | `all=true` subscribes to every event of this topic regardless of any other selector: the "all" bucket, and alone satisfies this route's "at least one selector" requirement. |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
Multiplex Subscribe [#multiplex-subscribe]
```http
GET /stream/subscribe
```
One multiplexed connection across several topics; `connected`, then per-topic `stalled`/`resumed` control frames.
| Parameter | In | Required | Notes |
| ------------------- | ----- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `select` | query | yes | Query parameters opening one cross-topic multiplexed subscription. `select` is a required, comma-separated list of `topic:dimension:value` triples (or `topic:all` for a topic's global bucket), e.g. `amm_events:token:0xAAA...,transfers:wallet:0xBBB...,blocks:all`. Valid topic names are `amm_events`, `blocks`, `transactions`, `logs`, `transfers`, `creations`, `launches`, `candles` (candle selectors use the series grammar of `/stream/candles`, and `candles` cannot be named in `resume`); valid dimensions are `token`, `pool` (a bare `0xcontract` or a V4 `0xmanager:0xpoolid`), `wallet`, `launchpad`, `address`, `topic0`, and `all`. `min_leg_magnitude` applies only to any `amm_events` selectors named (an error if given without one)). `resume` is an optional comma-separated `topic:v1.topicNamespace.sequence` list resuming a reconnecting consumer's bounded per-topic catch-up; a topic named in `resume` must also appear in `select`. It is the ONLY way to resume this route: a `Last-Event-ID` header without it cannot be attributed to a topic, so the connection is served live and told in band that the header was not honoured, rather than either resuming a guessed topic or being refused. |
| `min_leg_magnitude` | query | no | Query parameters opening one cross-topic multiplexed subscription. `select` is a required, comma-separated list of `topic:dimension:value` triples (or `topic:all` for a topic's global bucket), e.g. `amm_events:token:0xAAA...,transfers:wallet:0xBBB...,blocks:all`. Valid topic names are `amm_events`, `blocks`, `transactions`, `logs`, `transfers`, `creations`, `launches`, `candles` (candle selectors use the series grammar of `/stream/candles`, and `candles` cannot be named in `resume`); valid dimensions are `token`, `pool` (a bare `0xcontract` or a V4 `0xmanager:0xpoolid`), `wallet`, `launchpad`, `address`, `topic0`, and `all`. `min_leg_magnitude` applies only to any `amm_events` selectors named (an error if given without one)). `resume` is an optional comma-separated `topic:v1.topicNamespace.sequence` list resuming a reconnecting consumer's bounded per-topic catch-up; a topic named in `resume` must also appear in `select`. It is the ONLY way to resume this route: a `Last-Event-ID` header without it cannot be attributed to a topic, so the connection is served live and told in band that the header was not honoured, rather than either resuming a guessed topic or being refused. |
| `resume` | query | no | Query parameters opening one cross-topic multiplexed subscription. `select` is a required, comma-separated list of `topic:dimension:value` triples (or `topic:all` for a topic's global bucket), e.g. `amm_events:token:0xAAA...,transfers:wallet:0xBBB...,blocks:all`. Valid topic names are `amm_events`, `blocks`, `transactions`, `logs`, `transfers`, `creations`, `launches`, `candles` (candle selectors use the series grammar of `/stream/candles`, and `candles` cannot be named in `resume`); valid dimensions are `token`, `pool` (a bare `0xcontract` or a V4 `0xmanager:0xpoolid`), `wallet`, `launchpad`, `address`, `topic0`, and `all`. `min_leg_magnitude` applies only to any `amm_events` selectors named (an error if given without one)). `resume` is an optional comma-separated `topic:v1.topicNamespace.sequence` list resuming a reconnecting consumer's bounded per-topic catch-up; a topic named in `resume` must also appear in `select`. It is the ONLY way to resume this route: a `Last-Event-ID` header without it cannot be attributed to a topic, so the connection is served live and told in band that the header was not honoured, rather than either resuming a guessed topic or being refused. |
Mutate Selectors [#mutate-selectors]
```http
POST /stream/subscribe/{topic}/{id}/selectors
```
Mutates the filter set of an already-open subscription without reconnecting.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `topic` | path | yes | Stream topic name, as listed under Topics. |
| `id` | path | yes | Stream subscription id, minted by `GET /stream/subscribe` or a single-topic stream's `x-subscription-id` response header. |
| `add` | query | no | Query parameters mutating one already-open topic subscription of a live connection: `add` and `remove` are comma-separated `dimension[:value]` fragments (using `/stream/subscribe`'s selector grammar minus the topic prefix; the topic is the path segment here). Removals apply before additions, so swapping one selector for another in a single call never transiently exceeds the subscription limits. |
| `remove` | query | no | Query parameters mutating one already-open topic subscription of a live connection: `add` and `remove` are comma-separated `dimension[:value]` fragments (using `/stream/subscribe`'s selector grammar minus the topic prefix; the topic is the path segment here). Removals apply before additions, so swapping one selector for another in a single call never transiently exceeds the subscription limits. |
Transactions [#transactions]
```http
GET /stream/transactions
```
New transactions.
| Parameter | In | Required | Notes |
| ---------------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `wallets` | query | no | Query parameters selecting one transactions subscription's bounded watchlist. `wallets` matches either the transaction's origin or its recipient. |
| `all` | query | no | `all=true` subscribes to every event of this topic regardless of any other selector: the "all" bucket, and alone satisfies this route's "at least one selector" requirement. |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
Transfers [#transfers]
```http
GET /stream/transfers
```
New token transfers; each `transfer` frame carries `transactionHash`.
| Parameter | In | Required | Notes |
| ---------------- | ----- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tokens` | query | no | Query parameters selecting one transfers subscription's bounded watchlist: `tokens` and `wallets` (either leg). |
| `wallets` | query | no | Query parameters selecting one transfers subscription's bounded watchlist: `tokens` and `wallets` (either leg). |
| `all` | query | no | `all=true` subscribes to every event of this topic regardless of any other selector: the "all" bucket, and alone satisfies this route's "at least one selector" requirement. |
| `after_sequence` | query | no | Resumes a reconnecting consumer from its own last-delivered event sequence number, bounded by a fixed number of rows of durable catch-up; a consumer further behind than that must backfill via REST before resuming. |
---
# Addresses
URL: https://www.solscanner.app/docs/indexer/reference/addresses
Address summary, type, and label.
Small lookups that answer what an address is. A label is published only where the Indexer has evidence for it.
3 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Address Label [#address-label]
```http
GET /address/{address}/label
```
Any label recorded for the address.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Address Type [#address-type]
```http
GET /address/{address}/type
```
Whether the address is an EOA, a contract, or unobserved.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Address Summary [#address-summary]
```http
GET /addresses/{address}
```
Address summary: what kind of address it is and what is known about it.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
---
# Contracts
URL: https://www.solscanner.app/docs/indexer/reference/contracts
Block-bound runtime code observation.
A contract identity observation pinned to the block it was taken at, with explicit head lag and capture state.
1 route. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Runtime Identity [#runtime-identity]
```http
GET /contracts/{address}
```
Block-bound runtime code observation with explicit head lag and capture state.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
---
# Live RPC
URL: https://www.solscanner.app/docs/indexer/reference/rpc
Read-through to the node for the current answer rather than the indexed one.
These four routes bypass the index entirely and call the node. Use them when you need what is true right now rather than what is durably indexed. Each response is pinned to one observed block, requires the `rpc` scope, and returns a typed `503` when the bounded node read is unavailable.
4 routes. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Address Balance [#address-balance]
```http
GET /rpc/address/{address}/balance
```
The address's native balance right now, pinned to one observed block.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Address Code [#address-code]
```http
GET /rpc/address/{address}/code
```
Whether the address is a contract right now, and its runtime code hash, pinned to one observed block.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
Head [#head]
```http
GET /rpc/head
```
The node's own latest observed `(block, hash)`.
Token Metadata [#token-metadata]
```http
GET /rpc/tokens/{address}/metadata
```
Live ERC-20 probe (name, symbol, decimals, total supply) with the same typed absence vocabulary as the indexed metadata.
| Parameter | In | Required | Notes |
| --------- | ---- | -------- | -------------------------------- |
| `address` | path | yes | 0x-prefixed 20-byte hex address. |
---
# Batch
URL: https://www.solscanner.app/docs/indexer/reference/batch
Several reads answered from one shared snapshot.
Use this when two answers have to describe the same instant. Items in a batch cannot disagree with each other, because they are all read at one `(block, hash)`.
1 route. Every path below is served under `/index/v1/{chain}`, and every request carries an `x-api-key` header, so those two are not repeated per route. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Batch Request [#batch-request]
```http
POST /batch
```
Several reads in one request, answered from one shared snapshot so items cannot disagree with each other. Adds `token.metadata` (strict metadata + creation block + launch join, `point` cost), and `token.top_holders`, `token.holders` (`params.sort` = `balance` default or `wallet_id`) and `token.ohlcv` (`params.frame`/`from`/`to` required, `quote`/`order` optional; parsed by the standalone `/ohlcv` query struct and answered by its query facade): each the first page at a batch limit (20 holder rows, 50 candles), `page` cost. An unknown key inside `params` refuses the whole request with `422 invalid_json_body` rather than being dropped. Limits stay 20 items and a shared 4 MiB budget.
---
# Monitoring
URL: https://www.solscanner.app/docs/indexer/reference/monitoring
Liveness, readiness, checkpoints, gaps, and metrics.
Operator endpoints rather than consumer data. Checkpoints, gaps, and detailed health are chain-scoped; liveness and readiness are public process probes at the API root, while Prometheus metrics are served only on the separate loopback listener.
6 routes. `/health`, `/checkpoints`, and `/gaps` are served under `/index/v1/{chain}` and require the appropriate scope. `/health/live`, `/health/ready`, and `/metrics` are root process endpoints; the first two are public and metrics use the separate loopback listener. The shared contract for pagination, snapshots, precision and absence is in [Read API](/docs/indexer/api).
Checkpoints [#checkpoints]
```http
GET /checkpoints
```
Durable ingest checkpoints per lane.
Gaps [#gaps]
```http
GET /gaps
```
Open and resolved ingest gaps.
| Parameter | In | Required | Notes |
| --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor` | query | no | Opaque page cursor. See [Pagination](/docs/indexer/api). |
| `limit` | query | no | Page row cap. See [Pagination](/docs/indexer/api) for the shared contract; the exact default and maximum for this route are on the schema. |
| `order` | query | no | Sort direction: this route's accepted values and default are on the schema (`enum`/`default`); a route whose backing index has no reverse walk accepts only `asc`. See [Pagination](/docs/indexer/api) for the shared contract. |
Health Detail [#health-detail]
```http
GET /health
```
Scope-gated operator readiness: the complete `Readiness` DTO.
Live [#live]
```http
GET /health/live
```
Liveness probe. Unauthenticated.
This is a root process endpoint, not a chain-scoped route.
Ready [#ready]
```http
GET /health/ready
```
Readiness as a status code plus a boolean, with no operator detail. Unauthenticated.
This is a root process endpoint, not a chain-scoped route.
Prometheus [#prometheus]
```http
GET /metrics
```
Prometheus text exposition. Served only on the loopback metrics listener, never on the public API listener.
This is a root process endpoint, not a chain-scoped route.
---
# API keys
URL: https://www.solscanner.app/docs/api/keys
Create secret API keys, authenticate server-side requests, and understand errors, rate limits, billing, rotation, and IP allowlists.
The Scanner API lets your own servers run the same scans, traces, and lookups you use in the dashboard. You authenticate with a secret API key that you create yourself.
Who gets access [#who-gets-access]
API access is included on the **Scale** and **Enterprise** plans. Access is checked on every request against your current plan:
* An active Scale or Enterprise plan works while its billing period is current. A renewal has up to 3 days after the period ends to go through.
* A cancelled plan keeps working until the end of the period you paid for.
* When the plan lapses, or you move to a plan without API access, your keys are paused and return `API_PLAN_REQUIRED`. They start working again as soon as the plan does. You can still view and revoke paused keys.
See the [pricing page](/pricing) for plans.
Create a key [#create-a-key]
1. Open [API](/dashboard/api) in your dashboard.
2. Select **Create key**, give it a name (for example the service or environment that will use it), and optionally add an IP allowlist.
3. Copy the key and store it in your secret manager.
Keys look like `sk_live_` followed by 43 letters and numbers. **The full key is shown once**, right after you create or roll it. Scanner stores only a hash, so nobody can show it to you again. If you lose it, roll the key.
Each account can have up to 5 active keys. A key that is in its 24-hour rotation grace period doesn't count toward the limit.
Authenticate [#authenticate]
Send the key in a header on every request. Either form works:
```bash
curl -X POST "https://api.scanner.net/scan" \
-H "Authorization: Bearer $SCANNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"address":"WALLET_ADDRESS","depth":0}'
```
```bash
curl "https://api.scanner.net/v1/whoami" \
-H "x-api-key: $SCANNER_API_KEY"
```
Send one credential per request. A request that carries more than one (for example both an `Authorization` and an `x-api-key` header, or a secret key together with an embed key) is rejected with `400 INVALID_INPUT`.
Two rules are enforced, not just recommended:
* **Never put a key in a URL.** A request with a secret key in the query string or path is rejected with `API_KEY_IN_URL`, because URLs end up in logs, proxies, and browser history. If that happens, roll the key.
* **Never use a key in a browser.** Requests a browser makes (an `Origin` header together with the `Sec-Fetch-Mode: cors` or `navigate` or `Sec-Fetch-Site` headers browsers add) are rejected with `API_KEY_BROWSER_NOT_ALLOWED`. Call the API from your backend and pass results to your frontend. For a browser widget, use the [Embed API](/docs/embed/overview) instead.
Server-side HTTP clients that send an `Origin` header of their own, without the browser `Sec-Fetch-*` headers, are accepted.
Endpoints [#endpoints]
Keys work on the scanning and lookup endpoints, on both Solana and Robinhood Chain:
* wallet and token scans (`/scan`, `/contracts`, `/rh/scan`, `/rh/contracts`)
* funding chains and cross-chain traces (`/funding-chain`, `/cross-chain`)
* AI analysis of a Solana wallet scan (`/analyze`, 1 scan per analysis)
* the explorer (`/explorer`, `/rh/explorer`)
* address detection, identities, and wallet holdings (`/detect`, `/identities`, `/holdings`)
* the wallet finder (`/wallet-finder`, `/rh/wallet-finder`)
* alpha traders, KOL wallets, and bundles (`/alpha`, `/kol`, `/bundles`)
Account, billing, and admin endpoints are not available with a key and return `API_KEY_ROUTE_NOT_ALLOWED`. Indexer access is separate and is not covered by these keys.
Check a key [#check-a-key]
Two endpoints report on the key itself. Neither spends scans, but both count toward the key's rate limit.
* **`GET /v1/whoami`** returns the account and key the request authenticated as, the plan, and the effective requests-per-minute limit for that key.
* **`GET /v1/usage`** returns your remaining scans, the effective per-minute limit, and daily request counts grouped by endpoint and status class. It covers the last 30 days by default; pass `?days=` for anything from 1 to 90.
```bash
curl "https://api.scanner.net/v1/usage?days=7" \
-H "Authorization: Bearer $SCANNER_API_KEY"
```
Errors [#errors]
Every error has the same shape:
```json
{ "error": "Invalid API key", "code": "API_KEY_INVALID", "requestId": "..." }
```
| Status | Code | Meaning |
| ------ | ----------------------------- | ---------------------------------------------------------------------------------------------------------- |
| 400 | `API_KEY_IN_URL` | The key was sent in a URL. Send it in a header, and roll the key. |
| 400 | `INVALID_INPUT` | The request carried more than one credential. Send a single key in a single header. |
| 401 | `API_KEY_INVALID` | The key doesn't exist. Check for typos or whitespace. |
| 401 | `API_KEY_REVOKED` | The key was revoked, or its rotation grace period ended. |
| 403 | `API_KEY_BROWSER_NOT_ALLOWED` | The request came from a browser (`Origin` plus browser `Sec-Fetch-*` headers). Call the API from a server. |
| 403 | `API_KEY_ROUTE_NOT_ALLOWED` | This endpoint isn't available with an API key. |
| 403 | `API_KEY_IP_NOT_ALLOWED` | The request IP isn't on the key's allowlist. |
| 403 | `API_PLAN_REQUIRED` | Your plan doesn't include API access. The body includes `upgradeUrl`. |
| 403 | `FORBIDDEN` | API access has been switched off for your account. Contact [support](/dashboard/support). |
| 402 | `INSUFFICIENT_CREDITS` | Not enough scans left on your account for this request. |
| 429 | `API_KEY_RATE_LIMITED` | The key's per-minute limit was reached. Wait for `Retry-After` seconds. |
| 429 | `RATE_LIMITED` | Too many invalid keys from your IP address (30 per minute). Wait for `Retry-After` seconds. |
| 503 | `INTERNAL_ERROR` | Key verification is briefly unavailable. Retry after a short delay. |
Rate limits [#rate-limits]
Limits apply per minute and are shared across all Scanner servers. Two buckets are checked on every request: one for the key, and one for the whole account.
| Plan | Requests per minute |
| ---------- | --------------------------------------- |
| Scale | 120 |
| Enterprise | 600 by default, adjustable per contract |
The account bucket uses the same number, so this is the ceiling for the account as a whole. Five keys on a Scale plan share those 120 requests a minute, they do not get 120 each. `X-RateLimit-Limit` reports the lower of the two buckets for the key that made the request.
Every successful response includes `X-RateLimit-Limit` and `X-RateLimit-Remaining`. The window is rolling, so `X-RateLimit-Remaining` is an estimate when several requests with the same key arrive at once. A `429` also includes `Retry-After` with the number of seconds until a request fits in the window again. Back off and retry after that time rather than retrying immediately.
Billing [#billing]
API requests spend scans from your plan's allowance exactly like the dashboard: a Quick scan spends 1, Deep 2, Max 4, and a token scan 1. The full table is on the [Deep Scan page](/docs/scanner#scan-depth-and-cost). Cached results are free, failed scans refund automatically, and lookups that are free in the dashboard are free through the API.
The [API](/dashboard/api) page shows requests and scans spent per day and per key for the last 30 days. Usage can take up to a minute to appear.
Rotation [#rotation]
Roll a key from the dashboard to replace it with a new secret. The new key keeps the old key's name and IP allowlist. You choose what happens to the old key:
* **Keep old key working for 24 hours.** Use this for routine rotation: deploy the new key, and the old one stops on its own after 24 hours.
* **Revoke old key now.** Use this when a key has leaked. The old key stops working immediately.
**Revoke** stops a single key immediately. **Revoke all** stops every key on the account at once. Revoking or rolling a key, and plan changes, take effect on every server as soon as they are saved. If the servers briefly lose contact with each other, a change can take up to 5 seconds to reach all of them.
Creating, rolling, revoking, and revoking all keys each send an email to your account address with the key name, time, and IP address, so you'll know if someone else made the change. Renames and allowlist edits are recorded in the key activity list without an email, and accounts that signed in with a wallet and have no email address get the activity list alone.
IP allowlist [#ip-allowlist]
Add an allowlist to a key to accept requests only from your servers. Enter one IPv4 or IPv6 address, or CIDR range, per line, for example `203.0.113.10` or `198.51.100.0/24`. Leave it empty to accept any IP. Requests from other addresses get `API_KEY_IP_NOT_ALLOWED`.
* A key's allowlist holds at most 50 entries.
* A `/0` range is rejected. It would match every address, which is the same as having no allowlist.
* A single address is stored as a `/32` (IPv4) or `/128` (IPv6) range.
* Ranges are stored by their network address, so `10.0.0.5/8` is saved as `10.0.0.0/8`. Check the saved list if you entered a range with host bits set.
Activity list [#activity-list]
The activity list on the [API](/dashboard/api) page records key changes and blocked attempts: requests from an IP outside the allowlist, use of a revoked key, and use of a key while the plan doesn't include API access. Blocked attempts are recorded at most once every 10 minutes per key for each kind, so an entry tells you it happened, not how often. Use the per-day usage to judge volume.
Security practices [#security-practices]
* Store keys in a secret manager or environment variables, never in source control.
* Use a separate key per service or environment, so you can roll one without touching the rest.
* Add an IP allowlist when your servers have fixed egress addresses.
* Roll keys on a schedule, and immediately when someone with access leaves.
* Check the activity list on the [API](/dashboard/api) page for blocked attempts, such as a revoked key still being used.
* If a key leaks, roll it with **Revoke old key now**, then check usage for requests you don't recognize.
---
# Embed overview
URL: https://www.solscanner.app/docs/embed/overview
Put Scanner's interactive token map on your website.
The Embed API lets you show Solana and Robinhood Chain bubble maps on your own site via iframe. Both use the same embed key and allowed-domain settings. See [Plans and access](/docs/access) to get set up.
For token, wallet, transaction, and block detail pages, see [Explorer views](/docs/embed/explorer).
Quick start [#quick-start]
```html
```
Replace `TOKEN_MINT` with any Solana token mint or wallet address, and `YOUR_API_KEY` with your `emb_` key.
URL format [#url-format]
| Chain | Iframe URL |
| --------------- | ------------------------------------------------------------------------- |
| Solana | `https://www.scanner.net/sol/scanner/{ADDRESS}/map?embed=1&key={API_KEY}` |
| Robinhood Chain | `https://www.scanner.net/rh/scanner/{ADDRESS}/map?embed=1&key={API_KEY}` |
For Robinhood Chain, use a `0x` token contract or wallet address. Existing Solana URLs under `/scanner/{ADDRESS}/map` still redirect to `/sol/scanner/{ADDRESS}/map`.
| Parameter | Where | Required | What it does |
| --------- | ----- | -------- | -------------------------------------------------------------------------------- |
| `ADDRESS` | path | Yes | Solana mint/wallet or Robinhood Chain contract/wallet address |
| `embed` | query | Yes | Turns on embed mode. `?embed=1` is canonical; the bare flag `?embed` also works. |
| `key` | query | Yes | Your `emb_` API key |
The canonical host is `https://www.scanner.net`. The apex `scanner.net` redirects to `www`, so use `www` directly to avoid an extra hop inside the iframe.
Scan modes [#scan-modes]
Scan mode is optional. If omitted, the address is auto-detected (token → `holders`, wallet → `quick`).
**Token modes** (use with a mint or token contract):
| Parameter | What you get |
| --------- | ------------------------------------------------- |
| `holders` | Top holders with bundle detection, on both chains |
| `traders` | Top traders by realized PnL, Solana only |
Robinhood Chain does not support trader maps. Requesting `traders` shows an explanation and a **View holders** link; it does not run a different scan silently. Modes can also be passed as `mode=holders`, `mode=quick`, and so on.
**Wallet modes** (use with a wallet address):
| Parameter | Speed | What you get |
| --------- | ------ | ----------------- |
| `quick` | Fast | Basic connections |
| `deep` | Medium | Full graph |
| `max` | Slow | Maximum depth |
```
/sol/scanner/{MINT}/map?embed=1&key={KEY}&traders
```
What users see [#what-users-see]
Both maps render without Scanner navigation or login prompts, with zoom, pan, filters, and node details. In a Robinhood embed, **Scan this wallet** opens the selected wallet's map inside the iframe, retaining the embed key and wallet scan depth. Personal wallet-saving controls are hidden.
The Solana map also includes a "Powered by Scanner" link, cluster sidebar, and rescan controls. A rescan of the same address inside 20 seconds is refused by the server cooldown. These controls are not yet shared with the Robinhood map.
Error rendering [#error-rendering]
Scan failures render a visible error inside the iframe. A missing, invalid, or disabled key shows an error page inside the iframe instead of the map. The Robinhood map also explains unsupported trader mode rather than showing an empty graph. A successful scan with no connections shows an empty-state message.
Because `frame-ancestors` and cross-origin boundaries prevent the host page from reading the iframe's DOM or JSON responses, surface these states by watching for empty/stalled embed renders on your side (e.g. request timeout on `/embeds/validate` before mounting the iframe). See [Authentication](/docs/embed/authentication) for the validate endpoint.
Usage [#usage]
Results under 1 hour old are served from cache, so they run no new scan. Every embed request still counts one unit against your org's daily and monthly limits, cached or not. Once you are over the limit, requests that need a fresh scan return `429`.
Limits are set per org. Raise them in your [Telegram channel](/docs/access#support) or email [payments@scanner.net](mailto:payments@scanner.net).
Troubleshooting [#troubleshooting]
If you see a blank frame or error:
* Check your domain is in the allowed list (see [Authentication](/docs/embed/authentication))
* A missing or invalid key shows an error page inside the iframe. Verify the key is present in the iframe URL and correct (pre-flight with [validate](/docs/embed/authentication#validate-a-key))
* Confirm you haven't exceeded your usage limits
---
# Explorer views
URL: https://www.solscanner.app/docs/embed/explorer
Embed a token, wallet, transaction, or block from either chain.
Embed an individual Explorer view using the same `emb_` key and allowed-domain settings as the [map embed](/docs/embed/overview). The existing Explorer components provide balances, activity, transfers, charts, filters, pagination, and transaction previews where available for that view.
Quick start [#quick-start]
```html
```
Supported views [#supported-views]
Use `sol` for Solana or `rh` for Robinhood Chain:
| View | URL path |
| -------------- | --------------------------------------- |
| Token | `/embed/{CHAIN}/token/{ADDRESS}` |
| Wallet/address | `/embed/{CHAIN}/address/{ADDRESS}` |
| Transaction | `/embed/{CHAIN}/tx/{SIGNATURE_OR_HASH}` |
| Block | `/embed/{CHAIN}/block/{SLOT_OR_NUMBER}` |
Append `?embed=1&key={API_KEY}`. For Robinhood Chain, addresses and transaction hashes use their `0x` form. For Solana, use the mint, wallet address, signature, or slot number.
You can also append `?embed=1&key={API_KEY}` to a regular detail URL such as `/rh/address/{ADDRESS}`. It redirects to the corresponding `/embed/rh/address/{ADDRESS}` wrapper and preserves the query string. Regular detail pages without embed mode reject cross-origin framing.
Explorer homepages and Scanner account/tool pages are not embedded by this feature.
Navigation and view state [#navigation-and-view-state]
* Detail links and search results navigate inside the iframe and retain its key, including when changing chains.
* Tabs, filters, pagination, and browser history retain embed context. Existing view-specific query parameters continue to work.
* Links to the Explorer homepage, scanning tools, and account pages open a separate tab without passing the embed key. External links remain external.
* Site navigation, footer, and the personal pinboard are hidden. The Explorer search field and transaction previews remain available.
To switch the embedded view from your application, update the iframe's `src`:
```javascript
function explorerEmbedUrl(chain, view, value, apiKey) {
const params = new URLSearchParams({ embed: "1", key: apiKey });
return `https://www.scanner.net/embed/${chain}/${view}/${encodeURIComponent(value)}?${params}`;
}
```
Access and limits [#access-and-limits]
The server validates the embed key before rendering the view and applies the organization's `frame-ancestors` domain policy. Missing or invalid keys show an error instead of Explorer content; if validation is unavailable, the embed fails closed. Key-bearing page responses are private and not cached, while the underlying public Explorer data keeps its normal caching.
Explorer views use the existing public read APIs and their rate limits. Loading, filtering, or paging an Explorer view does not start a deep scan or spend map-scan credits. Map scans retain their separate [usage limits](/docs/embed/rate-limits).
See [Authentication](/docs/embed/authentication) for domain registration and key validation, and [Implementation guide](/docs/embed/implementation#csp-headers) for the host page's CSP and iframe sandbox settings.
---
# Authentication
URL: https://www.solscanner.app/docs/embed/authentication
API keys and domain whitelisting.
API key [#api-key]
You get one key per org. It starts with `emb_` and is 68 characters long. Keys can be viewed and rotated from the admin dashboard. After a rotation the old key stops working within 5 minutes, because key lookups are cached for that long. Plan the swap with that window in mind, and treat a leaked key as live until it has passed.
Pass it as a query param:
```
/sol/scanner/{ADDRESS}/map?embed=1&key=emb_a1b2c3d4e5f6...
/rh/scanner/{ADDRESS}/map?embed=1&key=emb_a1b2c3d4e5f6...
```
Embed keys are publishable: they sit in your page's HTML, so treat them like a public identifier rather than a secret. Your domain list controls where the map can be framed and filters API calls on a best-effort basis. Your usage limits are what bound spend on a key.
Domain whitelisting [#domain-whitelisting]
Both chains use the same key and domain settings.
**No domains configured (public mode).** If your org has no allowed domains, the key works on any site: the iframe can be framed anywhere and API calls are accepted from any origin. This is the default for a new org until you ask us to add domains.
**Domains configured.** Once at least one domain is set:
* The iframe response sets a `Content-Security-Policy: frame-ancestors` rule for your domains, and the browser blocks embedding on other sites.
* API calls from inside the iframe originate from Scanner itself, so the backend trusts Scanner's frontend origin while separately validating the embed key and quota.
* Direct API calls from other origins are checked against the allowed-domain list and receive `403 EMBED_DOMAIN_REJECTED` if rejected.
* Requests that carry the key but send neither an `Origin` nor a `Referer` header (for example `curl` or a server-side fetch) are rejected with `403 EMBED_DOMAIN_REJECTED`. Browsers always send `Origin` on the iframe's own API calls, so real embeds are unaffected.
Server-to-server calls [#server-to-server-calls]
Embed keys are for browsers. If you need Scanner data from your backend, use an `sk_live_` API key instead; see [API keys](/docs/api/keys). Header checks are a filter, not authentication: a script can send any `Origin` it likes, which is why quotas and rate limits apply to every key.
Matching rules:
| You register | Works on | Doesn't work on |
| ----------------- | ----------------------------------------------------------- | -------------------------- |
| `app.example.com` | `app.example.com` | `example.com`, `other.com` |
| `example.com` | `example.com`, `sub.example.com` | `other.com` |
| `localhost` | `http://localhost:3000`, `http://localhost:5173` (any port) | Remote origins |
| `localhost:5173` | `http://localhost:5173` only | Other ports |
If you add `example.com`, all subdomains work too. To add or change domains, contact us.
Local development [#local-development]
For dev environments, register either `localhost` (matches any port) or the specific `localhost:PORT` you run your integrator on. Both schemes `http://` and `https://` are accepted by the iframe's `frame-ancestors` CSP.
Validate a key [#validate-a-key]
Check if a key is valid without triggering a scan:
```
GET https://api.scanner.net/embeds/validate?key={API_KEY}
```
No auth needed. Returns:
```json
{ "valid": true, "domains": ["app.example.com", "staging.example.com"] }
```
Or if invalid:
```json
{ "valid": false, "domains": [] }
```
Use this before mounting the iframe to fail fast when a key is rotated, disabled, or typo'd: the iframe itself cannot report invalid-key state to the host page (cross-origin), so a pre-flight check is the cleanest way to surface it.
Errors [#errors]
| Status | Code | Meaning |
| ------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 403 | `EMBED_KEY_INVALID` | Bad key or key not found |
| 403 | `EMBED_ORG_DISABLED` | Org disabled by admin |
| 403 | `EMBED_DOMAIN_REJECTED` | The request's `Origin` (or `Referer`) is not on your domain list, or your org has domains set and the request sent neither header. Check the page's domain against your list; for server-to-server calls, use an `sk_live_` [API key](/docs/api/keys) |
---
# Implementation guide
URL: https://www.solscanner.app/docs/embed/implementation
Iframe setup, React component, and common patterns.
Basic iframe [#basic-iframe]
```html
```
Replace `TOKEN_MINT` and `YOUR_API_KEY`. Host is `https://www.scanner.net` (the apex `scanner.net` redirects to `www`).
For Robinhood Chain, use `/rh/scanner/0xADDRESS/map` instead of `/sol/scanner/TOKEN_MINT/map`. The key, domain settings, and iframe attributes stay the same. `holders`, `quick`, `deep`, and `max` work on both chains; `traders` is Solana-only.
Required `sandbox` tokens [#required-sandbox-tokens]
If your page applies a strict `sandbox` attribute, it must include at least:
| Token | Why |
| -------------------------------- | ------------------------------------------------------------------------------------------ |
| `allow-scripts` | The map is client-rendered React and requires JS to run |
| `allow-same-origin` | Preserves the iframe's origin for API requests; without it a sandbox uses an opaque origin |
| `allow-popups` | External explorer links open in new tabs |
| `allow-popups-to-escape-sandbox` | Lets those tabs open without inheriting the iframe sandbox |
Keep `allow-same-origin`. Without it the iframe gets an opaque origin, so its API calls send `Origin: null`, and the backend falls back to the `Referer` header to identify the caller. If your page or a browser setting also strips the referrer, those calls carry no usable origin, and an org with allowed domains rejects them with `403 EMBED_DOMAIN_REJECTED`.
React component [#react-component]
```tsx
function ScannerEmbed({
address,
chain = "sol",
apiKey,
mode,
height = 600,
className,
}: {
address: string;
chain?: "sol" | "rh";
apiKey: string;
mode?: "holders" | "traders" | "quick" | "deep" | "max";
height?: number | string;
className?: string;
}) {
const params = new URLSearchParams({ embed: "1", key: apiKey });
if (mode) params.set("mode", mode);
const src = `https://www.scanner.net/${chain}/scanner/${encodeURIComponent(address)}/map?${params}`;
return (
);
}
```
```tsx
```
Pass `chain="rh"` and a Robinhood Chain `0x` address to use the same component there. Do not pass `mode="traders"` for Robinhood Chain; that mode shows an unsupported-mode message.
Wallet scans [#wallet-scans]
Use a wallet address instead of a token mint. Depth is optional. If omitted, the embed runs a `quick` scan (same as passing `&quick`).
```html
```
| Param | Speed | What you get |
| ----------------- | ------ | ----------------- |
| `quick` (default) | Fast | Basic connections |
| `deep` | Medium | Full graph |
| `max` | Slow | Maximum depth |
Switching tokens dynamically [#switching-tokens-dynamically]
Update the iframe `src` to load a different address without reloading the page:
```javascript
function loadToken(address, chain = "sol") {
const params = new URLSearchParams({ embed: "1", key: API_KEY });
document.getElementById("scanner-embed").src =
`https://www.scanner.net/${chain}/scanner/${encodeURIComponent(address)}/map?${params}`;
}
```
Responsive sizing [#responsive-sizing]
For fluid layouts, use the aspect-ratio trick:
```html
```
CSP headers [#csp-headers]
If your site uses `Content-Security-Policy`, allow our host. Include both the apex and `www` so clients following the redirect still pass:
```
Content-Security-Policy: frame-src 'self' https://www.scanner.net https://scanner.net;
```
What's included [#whats-included]
Both chains include interactive maps with zoom, pan, drag, tooltips, node details, and filters. Robinhood's **Scan this wallet** action keeps navigation inside the embedded map with the same key. Solana additionally includes its cluster sidebar and rescan controls (a repeat scan of the same address inside 20 seconds is refused by the server cooldown).
It does not include Scanner navigation, login prompts, or plan upgrade UI.
---
# Rate limits
URL: https://www.solscanner.app/docs/embed/rate-limits
Request limits, cooldowns, and quota.
Three layers of rate limiting apply to every embed request. The first two refuse the request outright. The third, the org quota, only blocks work that needs a fresh scan, so an over-quota org can still be served from cache on the routes that check the cache first.
IP limit [#ip-limit]
7 requests per minute per IP, over a rolling 60 second window. It applies to embed requests, meaning requests carrying a `?key=` that resolved to an org.
Returns `429` with `RATE_LIMITED`.
Address cooldown [#address-cooldown]
20 seconds between requests for the same address per org. Prevents hammering the same target.
Token map scans check their cache first, so a cached token result is served without waiting out the cooldown. Wallet scans, funding chains, and cross-chain traces apply the cooldown before the cache lookup, so a repeat request for the same wallet inside 20 seconds is refused even when the answer was cached.
Returns `429` with `EMBED_MINT_COOLDOWN`.
Org usage limits [#org-usage-limits]
Your org has daily and monthly request caps set during onboarding. **Every embed request counts against them, cached or fresh.** A cached result is free in the sense that it runs no new scan, but it still spends one unit of the daily and monthly allowance.
Returns `429` with `EMBED_QUOTA_EXCEEDED` when exceeded.
Adjustments go through your [Telegram channel](/docs/access#support), or email [payments@scanner.net](mailto:payments@scanner.net).
Error examples [#error-examples]
```json
{ "error": "Too many requests", "code": "RATE_LIMITED" }
```
```json
{ "error": "Scan cooldown: 18s remaining", "code": "EMBED_MINT_COOLDOWN" }
```
```json
{ "error": "Embed quota exceeded", "code": "EMBED_QUOTA_EXCEEDED" }
```
Caching [#caching]
Scans are cached for 1 hour with up to 3 versions per token and scan mode. Within that window, requests return cached data instantly. After the cache expires, the next request runs a fresh scan.
Checking your usage [#checking-your-usage]
Contact us for current stats, or request admin dashboard access to see real-time numbers: daily/monthly usage vs limits, top mints, top domains.
---
# Plans and access
URL: https://www.solscanner.app/docs/access
Plans, scan allowances, $SCAN holder perks, API and Embed keys, Enterprise, and the direct Telegram channel that comes with Enterprise.
Looking for plans and prices? They live on the [pricing page](/pricing). This page explains how access works around them: what's free, how scans are spent, and how to get API, Embed, and Enterprise set up.
The Explorer, Wallet Finder, and trackers are free and need no account. Deep Scans, Seek, and the API run on your plan's scan allowance. The Embed API runs on its own per-organization limits instead.
Plans and scan allowances [#plans-and-scan-allowances]
Every plan comes with a monthly allowance of scans. Unused scans roll over and never expire. The free allowance tops up about a month after the last top-up; on a paid plan your plan allowance replaces it rather than stacking with it.
| Plan | What you get |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| Free | 30 scans every month, all scan depths |
| Individual, $89/mo | 600 scans every month |
| Team, $399/mo | 4,000 scans every month, priority support |
| Scale, $799/mo | 20,000 scans every month, API access included, onboarding help |
| Enterprise | Custom scan limits, API and Embed access sized to the integration, dedicated support, direct Telegram channel |
Two older plans are no longer sold but still honoured for existing subscribers: **Starter** ($30/mo, 120 scans every month) and **Professional** ($60/mo, 260 scans every month). If you're on one, it keeps its price and allowance until you change plans.
Yearly billing charges 10 months and grants the full 12 months of scans, so two months are free. Enterprise is arranged directly: email [payments@scanner.net](mailto:payments@scanner.net).
What each scan costs is on the [Deep Scan page](/docs/scanner#scan-depth-and-cost): a Quick scan spends 1, Deep 2, Max 4, and a token scan 1. Failed scans refund automatically.
Plans are paid by card through Stripe and renew until cancelled. Full current pricing is on the [pricing page](/pricing).
$SCAN holder perks [#scan-holder-perks]
Link a Solana wallet in [Settings](/dashboard/settings) and Scanner detects your $SCAN balance automatically:
| Holding | Perks |
| ------------------------ | ------------------------------------------- |
| 1M+ $SCAN | 100 free scans a month |
| 10M+ $SCAN (Mega Holder) | 600 free scans a month and 50% off any plan |
API and Embed access [#api-and-embed-access]
API access is included on Scale and Enterprise. It is self-serve: open [API](/dashboard/api) in your dashboard, create a secret key, and call the API from your server. You can hold up to 5 active keys, roll them, restrict them to IP ranges, and see usage per key. API calls spend scans from your plan's allowance at the same cost as the dashboard. Everything you need is in [API keys](/docs/api/keys).
Embed keys are different: they're public keys for the iframe embed, issued per organization with allowed domains and daily and monthly limits, and they're still set up by the team. To get one, email [payments@scanner.net](mailto:payments@scanner.net) with the domains you'll embed on. Embed calls run on the organization's own limits and don't draw down your plan's allowance. Start with the [Embed overview](/docs/embed/overview), then [Authentication](/docs/embed/authentication) and [Rate limits](/docs/embed/rate-limits).
Support [#support]
An active Enterprise plan comes with a direct Telegram channel with the Scanner team. Embed and API customers provisioned by hand can be granted the same channel on request.
To claim it, open [Billing](/dashboard/billing), switch to the **VIP Access** tab, and enter your Telegram username. The tab only appears on accounts that are eligible, meaning an active Enterprise plan or an account the team has granted access by hand. If you think you qualify and don't see it, contact [support](/dashboard/support). The team sets the chat up by hand, normally within 24 hours, and the tab shows the current state until it's ready.
On any other plan, reach us through the [support page](/dashboard/support).
---
# Your dashboard
URL: https://www.solscanner.app/docs/dashboard
Scan history, saved wallets, explorer bookmarks, investigations, billing, and account settings.
Everything tied to your account lives under [`/dashboard`](/dashboard): a summary view with your scan balance, recent scans, saved wallets, and payments, plus the detail pages below.
Billing and scan balance [#billing-and-scan-balance]
[Billing](/dashboard/billing) is the single page for money and usage: your current plan, scans remaining (they roll over and never expire), a link to change plan, 30-day usage stats with a daily chart, and full payment history with invoices. $SCAN holders see their holder status and perks here too. Plans and costs are covered in [Plans and access](/docs/access).
Scan history [#scan-history]
[History](/dashboard/history) keeps every scan you've run. Search by wallet address, expand any entry to revisit the full result, and delete entries you don't want kept. Each entry shows its depth (Quick, Deep, Max) and, on Solana, any KOL identity attached to the wallet.
Saved wallets [#saved-wallets]
[Saved wallets](/dashboard/saved-wallets) is your address book, up to 1,000 wallets:
* **Name and organize:** give wallets names, sort them into groups, and attach an optional confidence percentage.
* **Bulk import:** bring wallets in from CSV or JSON, including exports from GMGN, Padre, and Axiom.
* **Export:** download the whole list, or just the wallets you select, as JSON in GMGN, Axiom, or Padre format.
Saved names show up across Scanner wherever those wallets appear.
Explorer bookmarks [#explorer-bookmarks]
[Explorer bookmarks](/dashboard/explorer) collects everything you saved with the bookmark button while browsing the [Explorer](/docs/explorer): wallets and addresses, tokens, transactions, blocks, and pools, on Solana and Robinhood Chain.
* **Find:** search by label or address, and filter by kind and by chain.
* **Organize:** sort items into groups, then drag items onto a group, or select several and move them together. Deleting a group keeps its items and leaves them ungrouped.
* **Rename and remove:** rename any item's label, or remove items one by one or in bulk. Removing an item here also takes it off the Explorer pinboard.
* **Map in Seek:** with a group selected, open its wallets and pools as a new [Seek](/docs/seek) board.
Your account holds up to 500 bookmarks. Signed out, the Explorer keeps up to 50 on the device you're using, and they're added to your account the next time you sign in.
Investigations [#investigations]
[Investigations](/dashboard/investigations) lists your saved [Seek](/docs/seek) workspaces: open, rename, share (public or restricted, with Editor or Viewer roles), and delete them.
Settings [#settings]
[Settings](/dashboard/settings) covers:
* **Wallet linking:** link a Solana wallet so Scanner can detect your $SCAN holder status and apply perks automatically.
* **Telegram and Discord:** link your accounts to use the [bots](/docs/bots) with your scans, saved wallets, and alerts.
* **Account deletion:** permanent, and removes all associated data.
Feedback and support [#feedback-and-support]
[Feedback](/dashboard/feedback) goes straight to the team, and [Support](/dashboard/support) holds the FAQ plus contact routes. Enterprise plans get a direct Telegram channel, described in [Plans and access](/docs/access).
---
# Telegram and Discord bots
URL: https://www.solscanner.app/docs/bots
Look up wallets and tokens, browse KOL and alpha traders, and track group token calls from Telegram or Discord.
Scanner has a Telegram bot and a Discord bot. Both let you check wallets and tokens without leaving chat, browse the [KOL tracker](/docs/trackers) and alpha trader leaderboards, and keep a leaderboard of token calls in your group or server. They work on Solana and Robinhood Chain (0x addresses). Alerts, KOL, identity, and PnL tools are Solana-only for now.
The bots are included on every plan, Free included.
How scans work [#how-scans-work]
The bots don't render full scan reports in chat (the one exception is Telegram's `/bm`, below). When you ask for a scan, you get a reply with buttons that open it in Scanner: the live bubble map, the full report, and your scan history. The scan runs there and uses your plan's scan allowance like any other scan: Deep 2, Max 4, token 1. See [Deep Scan](/docs/scanner) for what each depth covers and [Plans and access](/docs/access) for allowances.
Lookups that aren't scans cost nothing and need no account: balances, holdings, trades, identities, token cards, and the KOL and alpha leaderboards.
Add a bot [#add-a-bot]
Telegram [#telegram]
* **Private chat:** open the Scanner bot in Telegram and send `/start`.
* **Group:** add the bot to the group like any other member. It posts a welcome message listing the group commands.
In a group, `/link` and `/credits` reply to you in a private message so your account details stay out of the channel. Start a private chat with the bot first, otherwise it can't message you.
Discord [#discord]
Run `/setup` to get the install link and see whether auto-scan is on for your server. The bot asks for these permissions: View Channels, Send Messages, Embed Links, Attach Files (for charts and maps), and Read Message History.
Server-only commands such as `/leaderboard` only work inside a server, not in DMs.
Link your Scanner account [#link-your-scanner-account]
Linking ties the bot to your Scanner account, so scans, saved wallets, and alerts belong to you.
1. Send `/link` to the bot. It replies with a one-time code and a link to your settings.
2. Open the link while signed in, or paste the code under Telegram or Discord in [Settings](/dashboard/settings).
Telegram codes expire after 10 minutes. You can unlink either account from the same Settings page.
Pasting addresses and tickers [#pasting-addresses-and-tickers]
You don't always need a command:
* **Token contract:** paste a Solana mint or a 0x token and the bot replies with a token card (price, market cap, holders, socials, and trading links). In a group or server, the paste also counts as a call for the leaderboard.
* **`$TICKER`:** a ticker like `$BONK` resolves to the token and gets the same card.
* **Wallet:** a pasted wallet gets a quick balance summary with a button to open a Deep Scan. The scan itself only runs when you open it.
Auto-replies are throttled per user and per group, so repeating the same address a few seconds apart won't flood the chat. If the bot doesn't react to pastes in your group or server, auto-replies are off there; the commands below still work, and on Discord `/setup` shows the current status.
Group call tracking [#group-call-tracking]
Every token pasted in a group or server is recorded as a call by the person who posted it first. The bot then follows the token's peak after the call.
| Command | What it shows |
| -------------- | ------------------------------------------ |
| `/leaderboard` | Members ranked by their best call |
| `/calls` | Recent token calls in this group or server |
| `/mycalls` | Your own call stats and rank |
Calls are tracked separately for each group or server.
Telegram commands [#telegram-commands]
Scanning [#scanning]
| Command | What it does |
| --------------------- | ---------------------------------------------------------------------------------------------------- |
| `/scan ` | Deep Scan a wallet or token. Accepts Solana addresses, token mints, `.sol` domains, and 0x addresses |
| `/scan ...` | Open up to 5 addresses at once |
| `/deepscan ` | Same as `/scan` |
| `/maxscan ` | Max depth wallet scan |
| `/contract ` | Token holder scan. Aliases: `/x`, `/z`, `/h`, `/nh` |
| `/c ` | Token trader scan (Solana only). Alias: `/cx` |
| `/bm ` | Holder bubble map image right in chat. This runs a token scan on your linked account |
Wallets [#wallets]
| Command | What it does |
| ----------------------- | -------------------------------------------------------- |
| `/holdings ` | Token holdings and portfolio value |
| `/balance ` | Quick liquid balance. Alias: `/w` |
| `/trades ` | Trade history and realized PnL (Solana) |
| `/who [more]` | Known identity or label for one or more wallets (Solana) |
KOLs and traders [#kols-and-traders]
| Command | What it does |
| -------------------------------------- | ---------------------------------------------- |
| `/kol ` | Look up a KOL: PnL, wallets, and a scan button |
| `/compare , , ...` | Compare up to 5 KOLs |
| `/alpha [1d\|7d\|30d]` | Top alpha traders leaderboard |
| `/recent` | Recently scanned wallets |
| `/submitkol ` | Nominate a wallet for the KOL directory |
You can also type `@` followed by the bot's username and a name in any chat to autocomplete KOLs inline.
Alerts and saved wallets [#alerts-and-saved-wallets]
These need a linked account.
| Command | What it does |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `/alert [trades\|transfers\|both] [label]` | Watch a Solana wallet. `trades` covers swaps, `transfers` covers transfers over $100, `both` is the default |
| `/alerts` | List your active alerts |
| `/unalert ` | Stop watching a wallet |
| `/save [name]` | Add a wallet to your [saved wallets](/docs/dashboard#saved-wallets) |
| `/saved` | List your saved wallets |
| `/unsave ` | Remove a saved wallet |
Account [#account]
| Command | What it does |
| --------------------- | --------------------------------------------- |
| `/link` | Link your Scanner account |
| `/credits` | Scans remaining, 30-day usage, and scan costs |
| `/history` | Your recent scans |
| `/feedback ` | Send feedback to the team |
| `/help` | All commands and tips |
Discord commands [#discord-commands]
The Discord bot uses slash commands, so Discord shows each command's options as you type.
| Command | What it does |
| ------------------------------------ | --------------------------------------------------------------- |
| `/start` | Bot overview |
| `/setup` | Install link, permissions, and auto-scan status for this server |
| `/help` | All commands |
| `/scan address` | Open a wallet or token scan in Scanner. Solana or 0x |
| `/contract`, `/x`, `/z` | Open a token holder scan |
| `/h`, `/nh` | Open a holder or notable-holder scan |
| `/c`, `/cx` | Open a token trader scan (Solana only) |
| `/bm` | Open a token bundle holder scan |
| `/balance`, `/holdings` | Native balance, liquid value, and token holdings |
| `/trades` | Recent trades and PnL (Solana only) |
| `/who addresses` | Known KOL or entity labels for up to 10 wallets (Solana only) |
| `/alpha period` | Top alpha traders for 1d, 7d, or 30d |
| `/recent` | Recently scanned wallets |
| `/kol query` | Search known KOL wallets |
| `/leaderboard`, `/calls`, `/mycalls` | Server call tracking |
| `/link` | Get a code to link your Scanner account |
| `/credits` | Link to billing in your dashboard |
Alerts, saved wallets, KOL comparison, and scan history are Telegram-only. On Discord, use the [dashboard](/docs/dashboard) for those.
---
# For AI agents and LLMs
URL: https://www.solscanner.app/docs/llms
Machine-readable versions of these docs, how to cite Scanner, and how to link straight into the product from an AI tool or agent.
These docs are published in plain-text forms made for AI assistants, coding agents, and retrieval pipelines. Use them instead of scraping the HTML.
What is available [#what-is-available]
| File | What it is | When to use it |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- |
| [`/llms.txt`](/llms.txt) | A short index: what Scanner is, the public pages, explorer URL patterns, and every docs page with a one-line description | Start here. Small enough to read on every run |
| [`/llms-full.txt`](/llms-full.txt) | Every docs page in one plain-text file | Load once when you want the whole docs in context |
| Any docs URL plus `.md` | One page as Markdown | Fetch only the page you need |
Every page has a Markdown twin at the same address with `.md` added:
```text
https://www.scanner.net/docs/explorer the page
https://www.scanner.net/docs/explorer.md the same page as Markdown
https://www.scanner.net/docs.md the docs home as Markdown
```
All three forms are built from the same source as the pages you are reading, so they never drift apart. They refresh about once an hour. Cache them rather than refetching on every request.
Citing Scanner [#citing-scanner]
Link to the regular page URL, not the `.md` or `.txt` file. The regular URL is the canonical one, and it is the one a person can read.
```text
Good: https://www.scanner.net/docs/seek
Avoid: https://www.scanner.net/docs/seek.md
```
Linking into the product [#linking-into-the-product]
Every explorer view has a stable URL, so an assistant can send someone straight to a wallet, token, transaction, or block. The full list of patterns is on [URL reference](/docs/urls).
Two rules prevent most mistakes:
* **Solana identifiers are base58. Robinhood Chain identifiers start with `0x`.** A Solana address belongs under `/sol`, a `0x` address under `/rh`. Never mix them.
* **Do not invent identifiers.** If you were not given a full address, signature, or hash, link to the explorer home or to [Wallet Finder](/docs/finder) instead of guessing.
The explorers, Wallet Finder, and the trackers are free and need no account, so those links work for anyone. Deep Scans and Seek need a signed-in account. See [Plans and access](/docs/access).
Calling Scanner from code [#calling-scanner-from-code]
There are three separate developer products. Each has its own keys, and a key for one does not work on another.
| Product | Use it to | Start at |
| ----------- | ----------------------------------------------------- | ------------------------------------------ |
| Scanner API | Run scans, traces, and lookups from your own servers | [API keys](/docs/api/keys) |
| Embed API | Put Scanner views inside your own product | [Embed overview](/docs/embed/overview) |
| Indexer | Read indexed chain data and subscribe to live streams | [Indexer overview](/docs/indexer/overview) |
Keys are secrets. An agent should read them from its environment, never write them into code, prompts, logs, or URLs it shares, and never ask a person to paste one into a chat.
Respect the documented rate limits and retry guidance on each product's pages. Do not drive the website itself with a headless browser to extract data. Use the API that matches the job.
What these docs cover [#what-these-docs-cover]
These docs explain what Scanner shows, what each result means for the person reading it, and how to use each product. They do not describe how detection works internally, and an assistant should not guess at it or present a guess as fact. When someone asks how a result was reached, point them to the result itself and to what the page says about reading it.
Reporting a problem [#reporting-a-problem]
If a page is wrong, out of date, or hard for a tool to parse, tell us through [Support](/dashboard/support) or the feedback link in the dashboard.