CRIUS Docs
API status Get API key
Build

Build a Robinhood Chain token screener

Build a Robinhood Chain token screener with the Crius API: clean token lists, batch profiles, a risk check per token, credits per refresh and working code.

Updated 3 min read
Short answer

Poll GET /v1/robinhood/tokens for the lists you show (top_volume, new, graduating, trending…) with filters such as clean=true and min_liquidity_usd, add holder and lock data with POST /v1/robinhood/tokens/batch (100 tokens per call), and check each new token once with /security. Every call costs 1 credit and works without a key at 2 requests per second.

A token screener is a table of Robinhood Chain tokens that users filter and sort: by volume, price change, liquidity, market cap, launchpad or risk. With the Crius API it is three calls: token lists for the rows and their trading stats, batch profiles for fields the list doesn’t carry (holder concentration, liquidity locks), and security once per token for a risk badge. All three are on the anonymous plan and cost 1 credit per call with a key.

Architecture

  1. Poller. A server job calls /tokens every 30 seconds for each list you show, with your base filters (clean=true, min_liquidity_usd, launchpad, window). Each call returns up to 100 rows with price, market cap, liquidity, holders and per-window stats.

  2. Enricher. For the tokens in those lists, one POST /tokens/batch per 100 tokens adds holders.top10_pct, holders.dev_pct, liquidity.status and liquidity.locked_pct.

  3. Risk cache. Tokens you haven’t seen in the last hour get a /security call; store risk, score and flags.

  4. API for your UI. Users’ filters and sorts run on that in-memory table, so a page view costs no credits. Optionally, a stream subscription to launches:all adds new tokens the moment they launch.

Endpoints used: GET/tokens POST/tokens/batch GET/token/{address}/security GET/prices

The rows: a token list with filters

Top 24h volume, clean tokens with $10k+ liquidity

Which Robinhood Chain tokens traded the most in the last 24 hours, leaving out rugs, wash trading and thin markets?

GET/v1/robinhood/tokens?list=top_volume&window=24h&clean=true&min_liquidity_usd=10000&limit=3
curl "https://api.crius.sh/v1/robinhood/tokens?list=top_volume&window=24h&clean=true&min_liquidity_usd=10000&limit=3"
Real response 200 Captured Oct 28, 2026 · 281 lines (arrays trimmed)
{
"data": [
{
"address": "0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec",
"buy_volume_usd": {
"1h": 284727,
"24h": 5466713,
"5m": 4788.48,
"6h": 2762630
},
"buyers": {
"1h": 237,
"24h": 1993,
"5m": 25,
"6h": 786
},
"buys": {
"1h": 837,
"24h": 16502,
"5m": 48,
"6h": 6856
},
"change_pct": {
"1h": 0.222526,
"24h": -1.4982,
"5m": -0.129097,
"6h": -1.39644
},
"created_block": 153563,
"created_ts": 1782222069300,
"decimals": 18,
"fdv_usd": 18950323,
"holders": 192282,
"launchpad": null,
"launchpad_name": null,
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": null,
"website": null
},
"liquidity_usd": 5692665,
"logo": null,
"market_cap_usd": 18950201,
"name": "NVIDIA • Robinhood Token",
"price_eth": 0.0909845,
"price_usd": 214.313,
"progress_pct": null,
"quote": "USDG",
"sell_volume_usd": {
"1h": 203391,
"24h": 6094025,
"5m": 54662.2,
"6h": 3389490
},
"sellers": {
"1h": 230,
"24h": 2643,
"5m": 31,
"6h": 1098
},
"sells": {
"1h": 724,
"24h": 18645,
"5m": 107,
"6h": 8674
},
"status": null,
"symbol": "NVDA",
"traders": {
"1h": 329,
"24h": 3513,
"5m": 45,
"6h": 1417
},
"txns": {
"1h": 1453,
"24h": 33049,
"5m": 145,
"6h": 14479
},
"volume_usd": {
"1h": 488118,
"24h": 11560738,
"5m": 59450.7,
"6h": 6152120
}
},
{
"address": "0x117cc2133c37b721f49de2a7a74833232b3b4c0c",
"buy_volume_usd": {
"1h": 280776,
"24h": 5360063,
"5m": 8943.58,
"6h": 3449730
},
"buyers": {
"1h": 173,
"24h": 1786,
"5m": 24,
"6h": 920
},
"buys": {
"1h": 596,
"24h": 10514,
"5m": 26,
"6h": 5156
},
"change_pct": {
"1h": 0.329135,
"24h": 0.110872,
"5m": 0.350832,
"6h": 0.71074
},
"created_block": 153733,
"created_ts": 1782222864140,
"decimals": 18,
"fdv_usd": 20226775,
"holders": 70703,
"launchpad": null,
"launchpad_name": null,
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": null,
"website": null
},
"liquidity_usd": 16569917,
"logo": null,
"market_cap_usd": 20226751,
"name": "SPDR S&P 500 ETF Trust • Robinhood Token",
"price_eth": 0.328203,
"price_usd": 773.079,
"progress_pct": null,
"quote": "USDG",
"sell_volume_usd": {
"1h": 342684,
"24h": 4970409,
"5m": 74111.8,
"6h": 3375154
},
"sellers": {
"1h": 218,
"24h": 1924,
"5m": 39,
"6h": 971
},
"sells": {
"1h": 881,
"24h": 11039,
"5m": 85,
"6h": 5766
},
"status": null,
"symbol": "SPY",
"traders": {
"1h": 329,
"24h": 2757,
"5m": 54,
"6h": 1416
},
"txns": {
"1h": 1396,
"24h": 20497,
"5m": 103,
"6h": 10312
},
"volume_usd": {
"1h": 623461,
"24h": 10330472,
"5m": 83055.3,
"6h": 6824884
}
},
{
"address": "0x4a0e65a3eccec6dbe60ae065f2e7bb85fae35eea",
"buy_volume_usd": {
"1h": 133427,
"24h": 4487530,
"5m": 801.518,
"6h": 1670779
},
"buyers": {
"1h": 118,
"24h": 1173,
"5m": 7,
"6h": 499
},
"buys": {
"1h": 452,
"24h": 14559,
"5m": 9,
"6h": 5347
},
"change_pct": {
"1h": -0.00703424,
"24h": -3.40455,
"5m": -0.0708291,
"6h": -0.22877
},
"created_block": 412861,
"created_ts": 1782740860100,
"decimals": 18,
"fdv_usd": 13549210,
"holders": 116370,
"launchpad": null,
"launchpad_name": null,
"links": {
"discord": null,
"farcaster": null,
"github": null,
"telegram": null,
"twitter": null,
"website": null
},
"liquidity_usd": 3716808,
"logo": null,
"market_cap_usd": 13549000,
"name": "Space Exploration Technologies Corp. Class A Common Stock • Robinhood Token",
"price_eth": 0.0611357,
"price_usd": 144.005,
"progress_pct": null,
"quote": "USDG",
"sell_volume_usd": {
"1h": 138415,
"24h": 5276839,
"5m": 11840.2,
"6h": 1775402
},
"sellers": {
"1h": 145,
"24h": 1584,
"5m": 15,
"6h": 693
},
"sells": {
"1h": 499,
"24h": 15226,
"5m": 25,
"6h": 5620
},
"status": null,
"symbol": "SPCX",
"traders": {
"1h": 180,
"24h": 1956,
"5m": 21,
"6h": 821
},
"txns": {
"1h": 887,
"24h": 28427,
"5m": 34,
"6h": 10519
},
"volume_usd": {
"1h": 271842,
"24h": 9764370,
"5m": 12641.7,
"6h": 3446181
}
}
],
"meta": {
"block": 100409896,
"block_age_ms": 750,
"block_ts": 1793225395000,
"chain": "robinhood",
"finalized_block": 100401329,
"lag_ms": 160,
"list": "top_volume",
"next_cursor": "3",
"safe_block": 100405028,
"stats_since": "chain_start",
"window": "24h"
}
}

What this tells you

  • Tokenized stocks lead: NVIDIA (0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec, volume_usd["24h"] 11,560,738), the S&P 500 ETF (0x117cc2133c37b721f49de2a7a74833232b3b4c0c, 10,330,472) and SpaceX Class A (0x4a0e65a3eccec6dbe60ae065f2e7bb85fae35eea, 9,764,370), all quoted in USDG (quote: "USDG"). A meme-token screener adds launchpad=pons (or another launchpad id) to the same call.
  • Each row has stats for 5m, 1h, 6h and 24h: NVIDIA’s 24h has 33,049 txns from 3,513 traders, with more sellers (2,643) than buyers (1,993) and change_pct of −1.5%.
  • holders (192,282 for NVIDIA), liquidity_usd, market_cap_usd and fdv_usd are on the row, so most screener columns need no second call.
  • launchpad, status and progress_pct are null for tokens not launched on a launchpad; for curve tokens they give the launchpad id, bonding / graduated and curve progress.
  • meta.next_cursor pages to the next rows; limit goes up to 100.
Token lists reference Runs from your browser on the anonymous plan Data looks wrong? Report it

The extra columns: batch profiles

Five fields for two tokens in one call

I have a list of addresses. How do I get just the fields my table needs?

POST/v1/robinhood/tokens/batch
curl -X POST "https://api.crius.sh/v1/robinhood/tokens/batch" \
-H "content-type: application/json" \
-d '{"tokens":["0x7b8892cea949bc60d494681d1bc3ea96071ab0b8","0x0bd7d308f8e1639fab988df18a8011f41eacad73"],"fields":["symbol","price.usd","price.change_pct.24h","market.market_cap_usd","market.liquidity_usd"]}'
Real response 200 Captured Oct 28, 2026 · 40 lines (arrays trimmed)
{
"data": {
"0x0bd7d308f8e1639fab988df18a8011f41eacad73": {
"market": {
"liquidity_usd": 73135530,
"market_cap_usd": 90409474
},
"price": {
"change_pct": {
"24h": 0.286017
},
"usd": 2355.49
},
"symbol": "WETH"
},
"0x7b8892cea949bc60d494681d1bc3ea96071ab0b8": {
"market": {
"liquidity_usd": 5611.8,
"market_cap_usd": 3773.42
},
"price": {
"change_pct": {
"24h": -0.171971
},
"usd": 0.00000388948
},
"symbol": "CHARGE"
}
},
"meta": {
"block": 100406780,
"block_age_ms": 575,
"block_ts": 1793225076000,
"chain": "robinhood",
"finalized_block": 100397649,
"lag_ms": 196,
"missing": [],
"safe_block": 100401329
}
}

What this tells you

  • fields takes dot paths into the token profile; the response has exactly those paths per token, keyed by address.
  • WETH (0x0bd7d308f8e1639fab988df18a8011f41eacad73) has market.liquidity_usd of 73,135,530; CHARGE (0x7b8892cea949bc60d494681d1bc3ea96071ab0b8) has 5,611.8 and a market cap of 3,773.42.
  • Unknown addresses go to meta.missing instead of failing the call (empty here), so one bad row never breaks a refresh.

Use it in your app

This server keeps the table in memory, refreshes it every 30 seconds and answers GET /screener?min_liq=50000&max_top10=30&sort=volume for your front end.

// screener.mjs: node screener.mjs, then GET http://localhost:8080/screener?sort=change (Node.js 22+)
import { createServer } from "node:http";
const API = "https://api.crius.sh/v1/robinhood";
const HEADERS = { "content-type": "application/json", ...(process.env.CRIUS_API_KEY && { "x-api-key": process.env.CRIUS_API_KEY }) };
const LISTS = ["list=top_volume&window=24h&clean=true&min_liquidity_usd=10000", "list=new&min_liquidity_usd=5000", "list=graduating"];
const FIELDS = ["holders.top10_pct", "holders.dev_pct", "liquidity.status", "liquidity.locked_pct"];
const rows = new Map(); // address -> row
const risk = new Map(); // address -> { risk, score, flags, at }
const call = async (path, body) => {
const res = await fetch(`${API}${path}`, body ? { method: "POST", headers: HEADERS, body: JSON.stringify(body) } : { headers: HEADERS });
return (await res.json()).data;
};
async function refresh() {
for (const q of LISTS) for (const t of (await call(`/tokens?${q}&limit=100`)) ?? []) rows.set(t.address, { ...rows.get(t.address), ...t, seen: Date.now() });
for (const [a, r] of rows) if (Date.now() - r.seen > 10 * 60_000) rows.delete(a); // drop rows no list returned for 10 min
const addrs = [...rows.keys()];
for (let i = 0; i < addrs.length; i += 100) {
const extra = (await call("/tokens/batch", { tokens: addrs.slice(i, i + 100), fields: FIELDS })) ?? {};
for (const [a, p] of Object.entries(extra)) if (rows.has(a)) Object.assign(rows.get(a), { top10_pct: p.holders?.top10_pct, dev_pct: p.holders?.dev_pct, lp: p.liquidity?.status, locked_pct: p.liquidity?.locked_pct });
}
for (const a of addrs.filter((a) => Date.now() - (risk.get(a)?.at ?? 0) > 3_600_000).slice(0, 50)) {
const s = await call(`/token/${a}/security`);
if (s) risk.set(a, { risk: s.risk, score: s.score, flags: s.flags, at: Date.now() });
}
}
const SORTS = { volume: (r) => r.volume_usd?.["24h"] ?? 0, change: (r) => r.change_pct?.["24h"] ?? -Infinity, liquidity: (r) => r.liquidity_usd ?? 0, mcap: (r) => r.market_cap_usd ?? 0 };
createServer((req, res) => {
const q = new URL(req.url, "http://x").searchParams;
const out = [...rows.values()]
.map((r) => ({ ...r, ...risk.get(r.address) }))
.filter((r) => (r.liquidity_usd ?? 0) >= Number(q.get("min_liq") ?? 0))
.filter((r) => r.top10_pct == null || r.top10_pct <= Number(q.get("max_top10") ?? 100))
.filter((r) => r.risk !== "critical" && (q.get("risk") !== "low" || r.risk === "low"))
.sort((a, b) => (SORTS[q.get("sort")] ?? SORTS.volume)(b) - (SORTS[q.get("sort")] ?? SORTS.volume)(a))
.slice(0, 100);
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify(out));
}).listen(8080);
const loop = () => refresh().catch(console.error).finally(() => setTimeout(loop, 30_000));
loop();

What it costs

Operation Endpoint Credits
One list page (up to 100 tokens) GET /tokens 1
Extra fields for up to 100 tokens POST /tokens/batch 1
Risk for one token GET /token/{address}/security 1
Prices only, up to 100 tokens GET /prices 1

With three lists refreshed every 30 seconds, the lists cost 8,640 credits a day and the batch calls about the same again (up to 300 tokens, three calls per refresh). Re-checking the risk of up to 300 rows once an hour adds at most 7,200 a day. That’s about 740,000 credits a month, inside the Free plan’s 1,000,000; refreshing every 60 seconds halves the list and batch part. Without a key the same calls run on the anonymous plan at 2 requests per second, which is enough for a prototype, not for a refresh of this size. See Plans & limits.

Frequently asked questions

How do I keep rugs and wash trading out of a screener?

Add clean=true to any list. It applies the trending list's checks: no crash of 90% or more from the window's peak, $1,000+ liquidity, 20+ real holders, no insider or wallet-farm launch, no bot distribution, and no single wallet holding 40%+ of the liquidity unlocked. Then show each token's /security risk next to it.

How many tokens can I fetch per call?

Up to 100 per page from /tokens (paginate with cursor), and up to 100 per call from /tokens/batch and /prices. Each of those calls costs 1 credit regardless of how many tokens it returns.

Can a screener update in real time?

Lists are a poll: refreshing every 15–60 seconds is enough for most screeners. For live rows, subscribe to token:<address> on the WebSocket stream (price and holder count changes) for the tokens on screen, within your plan's subscription limit.

Which windows do the list stats cover?

List rows carry volume_usd, buy_volume_usd, sell_volume_usd, txns, buys, sells, buyers, sellers, traders and change_pct for 5m, 1h, 6h and 24h. window (for example window=1h) chooses which one ranks the list.

Get your free Crius API key

Every example on this page works with a free key: higher rate limits than the anonymous plan, holders, wallets and the WebSocket stream. No card needed.

  • Robinhood Chain (4663) only, from our own archive nodes
  • REST + WebSocket
  • Pay on-chain or by card