# Read API

Source: 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.
