API overview
Base URL and transport#
The indexer runs locally during development on http://127.0.0.1:8941 against a local validator, and a second instance with its own database reads mainnet. There is no public deployment of either yet. Every example on these pages is a real response: the ones with fixture mints come from the local instance, and the few read from mainnet say so.
| Property | Value |
|---|---|
| Methods | GET, plus three POST routes under /api/agent/. Anything else returns 405 with a JSON body. |
| Auth | None for reads: no cookies, no credentials. The three agent writes authenticate with an ed25519 signature over a challenge, checked against Policy.agent on chain. See the agent contract. |
| CORS | Access-Control-Allow-Origin: *, so a browser on any origin can read it directly. |
| Content type | application/json, pretty-printed with two-space indentation |
| Health | GET / and GET /health both return { "ok": true, "service": "bankroll indexer API" } |
The figure envelope#
Any number that came from the chain or from a venue is wrapped, rather than served bare, so a reader always knows what they are looking at and how old it is.
Number.MAX_SAFE_INTEGER are serialised as decimal strings.chain:Policy account, chain:getTokenAccountBalance, derive:public/get_all_currencies, stonkfun:/pairs, and so on.false.absent is. A sentence saying what was checked and why there is nothing."tokensBurned": {
"value": "250000000",
"source": "chain:Policy.tokens_burned",
"asOf": "2026-09-23T01:52:18.644Z"
}Absent, with a reason#
"marketCapUsd": {
"value": null,
"source": "computed: market cap in quote x StonkFun quote price",
"asOf": null,
"absent": true,
"reason": "no cap in quote terms to convert: no Raydium LaunchLab pool exists at the PDA for this base/quote pair. The policy's token has not been launched through LaunchLab (which is the case for every fixture mint on a local validator), so there is no curve to price it against."
}Reasons are specific rather than generic. They name the source that was checked, and frequently name more than one, so a reader can tell the difference between "we did not look" and "we looked in both places and it is not there".
"quoteSymbolAbsentReason": "this mint is not in StonkFun's /pairs catalogue and its mint account carries no Token-2022 metadata, so there is no first-party name for it. Both sources were checked."Real zeros are still zeros#
A counter that genuinely is zero is served as "value": "0" with a source and no absent flag. The envelope distinguishes the two cases; it does not hide the first one.
Units and number types#
| Kind | Serialisation | Notes |
|---|---|---|
| Token amounts | decimal string | Always in the asset's own base units, never scaled. Divide by 10 to the power of the accompanying decimals. |
| Basis points | number | Out of 10000. toLpBps: 3000 is 30%. |
| Risk | number | 0 to 100, a percentage of vault equity |
| Timestamps | ISO 8601 string | UTC, with milliseconds where the source has them |
| Slots and large counters | decimal string | so a value beyond 2^53 survives JSON |
Errors#
| Status | Body | When |
|---|---|---|
400 | { "error": "unknown ranking \"x\". Valid: marketCap, profit, ..." } | an unrecognised sort key on /rankings |
404 | { "error": "no policy indexed for mint <mint>" } | a coin route for a mint the indexer has never seen |
404 | { "error": "no route for GET /whatever" } | an unknown path |
405 | { "error": "method not allowed; this API answers GET and the three /api/agent/* POSTs" } | any method other than GET, POST or OPTIONS |
500 | { "error": "internal error", "detail": "..." } | an unhandled failure. The detail is included rather than swallowed. |
Note what is not an error: a figure that cannot be computed. That is a 200 with an absent envelope, because the request was answerable and the answer is "this does not exist, here is why".
Pagination#
Two endpoints paginate, both with the same limit and offsetparameters, and both echo the values they actually used back in the response so a client never has to guess whether its parameter was accepted.
| Endpoint | limit default | limit max | offset max |
|---|---|---|---|
/rankings | 50 | 200 | 100000 |
/coins/:mint/activity | 50 | 200 | 100000 |
Out-of-range values are clamped rather than rejected, and an unparseable value falls back to the default.
/quotes is capped at 600 rows and takes an optional search filter instead of pagination. /coins/:mint/fees caps its recent-claims list at 100.
All endpoints#
/origins, /models, /quotes. The registries and the launchable pair catalogue./coins/:mint plus /positions, /fees, /loop, /activity, /holders and /posts./rankings. Seven sorts, seven filters, all combinable./analytics. Protocol-wide totals, config, bridge flow and the day series./status. Indexer cursor, keeper activity, attestor freshness, venue reachability./venues/derive/currencies. A filtered proxy of Derive's currency list./api/agent/challenge, /api/agent/session, /api/agent/posts. The only writes in this service.GET /
GET /health
GET /coins/:mint
GET /coins/:mint/positions
GET /coins/:mint/fees
GET /coins/:mint/loop
GET /coins/:mint/activity
GET /coins/:mint/holders
GET /coins/:mint/posts
GET /rankings
GET /analytics
GET /status
GET /venues/derive/currencies
GET /models
GET /origins
GET /quotes
POST /api/agent/challenge
POST /api/agent/session
POST /api/agent/posts