Skip to content
Indexer Access

Read 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

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

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

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

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

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

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:

BoundMaximum
Rows1000
Rows when a USD sibling is published200
Rows when each row may require a separate mark100
Serialized response4 MiB

Responses identify the bound that stopped the page, so an exhausted dataset is distinguishable from a page limited by size.

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:

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.

Absence

Null carries a reason and never means zero. What it stores covers this and the other derivation rules.

Error envelope

Errors use the same JSON shape at framework and handler boundaries:

{"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

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.

GroupRoutesCovers
Tokens31Catalogue, search, detail, holders, transfers, swaps, traders, stats, ticks, candles, market cap, all-time high and low, liquidity, venue resolution, authority events, launches
Pools20Identity, swaps, ticks and bars, candles, stats, liquidity, extremes, and the V4 set keyed by manager and pool id
Wallets18Assets, portfolio, positions, PnL and daily PnL, trades, transfers, transactions, activity, internal, first seen, funding graph, cohorts
Chain13Blocks by number and hash, timestamp lookup, block transactions and logs, transaction envelope, receipt, logs, decoded logs, calldata, decoded call trees, and internal frames
Streams11Live topics, live candles, and multiplexed subscription
Screener and rankings3Filtered sorted views, plus self-describing named rankings
Cohorts4Cohorts for a token, plus metadata, members and evidence edges for one
Addresses3Summary, type, label
Live RPC4Node head, address code, native balance, and a live token metadata probe
Contracts1Block-bound runtime code observation
Batch1Several reads answered from one shared snapshot
Monitoring6Liveness, readiness, checkpoints, gaps, metrics

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

{
  "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

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

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.

View this page as Markdown