CRIUS
HoldersSnapshotsHistorical dataTutorial

Robinhood Chain Token Holders API: Live and Historical Snapshots

Get the holders of any Robinhood Chain token, live or at any past block or market cap, with cohorts and holder-count history. Code in Node.js and Python.

Crius 8 min read

The Crius token holders API returns the current holders of any ERC-20 on Robinhood Chain, and the holders as they were at any past block, timestamp, market cap or lifecycle moment, valued at that moment’s price. On top of snapshots it answers the two questions raw transfer logs make hard: which wallets held continuously through a window, and how the holder count moved over time.

This guide covers the four holder endpoints, the point-in-time syntax they share, and two complete scripts you can run against api.crius.sh today. If you only need a quick concentration check, the token profile has a holder summary; everything below is for when you need the actual wallets.

Why holder data is hard to build yourself

A token’s holder list is the running sum of every Transfer event since the contract was deployed. Computing the current list from an RPC node means replaying the whole log history; computing it as of block 84,387,222 means doing it again with a cut-off, and doing it as of “the first time market cap crossed $100k” means you also need a price for every block. On Robinhood Chain, where blocks arrive roughly every 100 ms, a busy token accumulates millions of transfers within weeks.

Crius keeps balances for every token from block 0 and replays transfer history server-side, so each of these is one HTTP request. Every response carries "history": "complete" when the token’s full history is indexed, and meta.block tells you exactly which block the answer reflects.

The four endpoints

Endpoint What it answers Plan Credits
/holders Who holds the token right now? Free 2
/holders/snapshot Who held it at block N, time T or market cap X, and what did they do since? Free 5
/holders/cohort Who held continuously through a window without selling? Starter 10
/holders/history How did the number of holders change over time? Starter 10

All four live under https://api.crius.sh/v1/robinhood/token/{address}/ and need an API key in the x-api-key header. A free key covers live holders and snapshots.

Live holders

Terminal window
curl "https://api.crius.sh/v1/robinhood/token/0xc9675fe679c7d4e1b94416f66d61c96127eb964a/holders?limit=5" \
-H "x-api-key: $CRIUS_API_KEY"

The response ranks holders by balance and labels infrastructure, because on-chain, pools and lockers are holders too. For a large token the first row is often the Uniswap v4 PoolManager, which custodies the liquidity of every v4 pool:

{
"count": 57727,
"distribution": { "top10_pct": 19.8791 },
"history": "complete",
"holders": [
{ "rank": 1, "address": "0x8366a39cc670b4001a1121b8f6a443a643e40951",
"label": "uniswap_v4_pool_manager", "balance": 50934240.59, "pct": 5.13987 },
{ "rank": 2, "address": "0x8f62…80a3", "label": null, "balance": 31288190.83, "pct": 3.15735 }
]
}

That is a real response for one of the most-held memecoins on the chain at time of writing (Oct 2026): 57,727 holders, top ten holding about 20% of supply. When you calculate concentration, decide whether infrastructure counts. Most analyses should exclude it, which is what exclude=infra does on the historical endpoints.

Point-in-time syntax

Snapshots and cohorts share one way to name a moment:

Form Meaning
block:84387222 An exact L2 block
ts:1791572442200 The last block at or before a unix-millisecond time
mcap:100000 The block where market cap first crossed $100k (mcap_crossing=last for the most recent crossing)
launch, trading_open, graduation, ath, now Lifecycle labels

Market-cap points are the useful ones in practice. “Holders at $100k market cap” is comparable across tokens in a way that “holders at block 84 million” is not.

Snapshots: who held at a moment, and what they did since

compare_to=now adds a compare block to every holder, with their balance now and a status of unchanged, increased, reduced or exited. Each holder also gets a cost_basis_usd and unrealized_pnl_usd from their own buys.

Terminal window
curl "https://api.crius.sh/v1/robinhood/token/0xc9675fe679c7d4e1b94416f66d61c96127eb964a/holders/snapshot?at=mcap:100000&compare_to=now&exclude=infra&limit=2" \
-H "x-api-key: $CRIUS_API_KEY"
{
"snapshot": { "at": "mcap:100000", "block": 84387222, "ts": 1791572442200 },
"price_at_snapshot": { "usd": 0.000101365, "eth_usd": 2481.01 },
"holders_count": 206,
"distribution": { "top10_pct": 18.2298, "top10_ex_infra_pct": 6.70094 },
"holders": [
{ "rank": 1, "address": "0x487c…930d", "balance": 7540817.38, "pct": 0.754,
"value_usd": 764.38, "cost_basis_usd": 584.52, "unrealized_pnl_usd": 179.86,
"compare": { "to": "now", "balance_now": 7495997.01, "change_pct": -0.594, "status": "reduced" } },
{ "rank": 2, "address": "0x4987…56ea", "balance": 7236749.46, "pct": 0.724,
"compare": { "to": "now", "balance_now": 7236749.46, "change_pct": 0, "status": "unchanged" } }
]
}

Note the two concentration figures: the top ten held 18.2% of supply, but only 6.7% once the pool and other infrastructure are excluded.

The script below pages through the whole snapshot (meta.next_cursor) and summarises what the early holders did. It’s the basis of “are early buyers still in?” checks:

// holders.mjs: who held at $100k market cap, and what did they do since? (Node 18+)
const API = "https://api.crius.sh/v1/robinhood";
const KEY = process.env.CRIUS_API_KEY;
const TOKEN = process.argv[2] ?? "0xc9675fe679c7d4e1b94416f66d61c96127eb964a";
async function get(path) {
const res = await fetch(`${API}${path}`, { headers: { "x-api-key": KEY } });
const body = await res.json();
if (!res.ok) throw new Error(`${res.status} ${body.error?.code}: ${body.error?.message}`);
return body;
}
async function snapshot(at) {
const holders = [];
let cursor = "";
do {
const q = new URLSearchParams({ at, compare_to: "now", exclude: "infra", limit: "500" });
if (cursor) q.set("cursor", cursor);
const { data, meta } = await get(`/token/${TOKEN}/holders/snapshot?${q}`);
holders.push(...data.holders);
cursor = meta.next_cursor ?? "";
} while (cursor);
return holders;
}
const early = await snapshot("mcap:100000");
const byStatus = {};
for (const h of early) byStatus[h.compare.status] = (byStatus[h.compare.status] ?? 0) + 1;
const stillIn = early.filter((h) => h.compare.balance_now > 0);
console.log(`holders at $100k market cap: ${early.length}`);
console.log("what they did since:", byStatus);
console.log(`still holding: ${stillIn.length} (${((stillIn.length / early.length) * 100).toFixed(1)}%)`);

Run against the token above, it printed:

holders at $100k market cap: 205
what they did since: { reduced: 40, unchanged: 150, exited: 14, increased: 1 }
still holding: 191 (93.2%)

A token where most early holders are exited tells a very different story from this one, where 150 of 205 never touched their position.

Snapshots also take tag (sniper, whale, dev…), min_usd and min_pct filters, so “snipers who held at graduation” is a single request: at=graduation&tag=sniper.

Cohorts: who held through a window

A snapshot is one moment; it can’t tell a wallet that held for two weeks from one that sold and bought back an hour ago. Cohorts can. You define what counts as a holder (min_usd, min_balance or min_pct), the window (from, to), and what breaks continuity with reset_on:

  • sell (default): any sell resets the streak
  • any_decrease: transfers out count too
  • below_threshold: only dropping below your size threshold counts
  • none: just check the start and end
"""cohort.py: wallets that held at least $50 continuously from $100k market cap until now."""
import os, sys, requests
API = "https://api.crius.sh/v1/robinhood"
TOKEN = sys.argv[1] if len(sys.argv) > 1 else "0xc9675fe679c7d4e1b94416f66d61c96127eb964a"
s = requests.Session()
s.headers["x-api-key"] = os.environ["CRIUS_API_KEY"]
r = s.get(f"{API}/token/{TOKEN}/holders/cohort", params={
"from": "mcap:100000", "to": "now", "min_usd": 50,
"continuous": "true", "reset_on": "sell", "exclude": "infra", "sort": "hold_time", "limit": 100,
}, timeout=60)
r.raise_for_status()
c = r.json()["data"]
print(f"qualified at the start: {c['started_qualified']}, held throughout: {c['qualified']}")
for h in c["holders"][:5]:
days = h["hold_time_s"] / 86400
print(f"{h['address'][:10]}… held {days:.1f} d, entry mcap ${h['entry_mcap_usd']:,.0f}, "
f"PnL ${h['pnl_usd']:,.0f}, tags {h['tags']}")
hist = s.get(f"{API}/token/{TOKEN}/holders/history",
params={"interval": "1d", "min_usd": 10, "exclude": "infra"}, timeout=60).json()["data"]
print(f"holders >= $10: min {hist['summary']['min']}, max {hist['summary']['max']}, now {hist['summary']['now']}")

Output at time of writing:

qualified at the start: 205, held throughout: 151
0xbf0ba9eb… held 14.7 d, entry mcap $101,365, PnL $5,596, tags []
0x369600ed… held 14.7 d, entry mcap $101,365, PnL $5,059, tags []
0xf07e29f8… held 14.7 d, entry mcap $101,365, PnL $6,204, tags []
holders >= $10: min 1, max 643, now 614

Each cohort row carries qualified_since_block, min_balance_in_window, sells_in_window and a reset_history, so you can explain why a wallet is in or out of the list. That matters for airdrop and reward lists, where the cohort is effectively the eligibility rule.

Holder-count history

/holders/history returns columnar arrays per interval (t, holders, min, max, new, exited) using your definition of a holder. Two details worth knowing:

  1. min_usd is converted at the current price. “At least $10” means at least the number of tokens worth $10 today, applied to every interval, so the definition doesn’t drift with price.
  2. new and exited count crossings, not net change. On an actively traded token, wallets cross your threshold many times a day, so these can be far larger than the change in holders. Use holders (or min and max) for the curve and the crossings to gauge churn.

Limits to plan around

  • Very large tokens. Historical replays (snapshots at a past point, cohorts) are not available for tokens with more than about 2 million transfers. The API answers window_too_large and points you to live /holders. On Robinhood Chain that means a handful of the largest assets.
  • Heavy endpoints. Snapshots, cohorts and history are computed on demand. Keep concurrency modest (a few requests in flight per key), cache results for at least 30 seconds, and retry 503 responses after their Retry-After.
  • Infrastructure. Always decide on exclude=infra; it changes concentration numbers dramatically, as the 18.2% vs 6.7% example shows.

Frequently asked questions

Can I get the holders of a token at a specific past block?

Yes. Call /holders/snapshot?at=block:<number>, or use ts:<unix ms> for a time. The response is valued at that block’s price and includes compare_to=now changes if you ask for them.

How do I get holders at a market cap level instead of a block?

Use at=mcap:<usd>. By default this is the first block where the token’s market cap crossed that level; mcap_crossing=last picks the most recent crossing.

Does the holder count include liquidity pools and burn addresses?

The live list includes them, labelled. Historical endpoints accept exclude=infra to leave out pools, LP lockers, launchpad contracts, routers and burn addresses. The snapshot response reports concentration both ways.

How far back does holder history go?

To block 0. Crius indexes Robinhood Chain from genesis, and responses mark "history": "complete" when a token’s full history is available.

Which plan do I need?

Live holders and snapshots work on the Free plan. Cohorts and holder-count history need Starter or higher. See plans and limits.

Build with Crius

Robinhood Chain data in one API

Free plan with 1M credits a month. Your key in under a minute.

Related APIs

More from the blog