# 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.

Source: https://docs.crius.sh/guides/build-a-token-screener/ · Updated 2026-10-28

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.

[GET `/tokens`](https://docs.crius.sh/reference/discovery/token-lists/) [POST `/tokens/batch`](https://docs.crius.sh/reference/tokens/batch-token-profiles/) [GET `/token/{address}/security`](https://docs.crius.sh/reference/security/security-checks/) [GET `/prices`](https://docs.crius.sh/reference/tokens/batch-prices/)

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](https://docs.crius.sh/reference/discovery/token-lists/) for the rows and their trading stats, [batch profiles](https://docs.crius.sh/reference/tokens/batch-token-profiles/) for fields the list doesn’t carry (holder concentration, liquidity locks), and [security](https://docs.crius.sh/reference/security/security-checks/) 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`](https://docs.crius.sh/reference/discovery/token-lists/) [POST`/tokens/batch`](https://docs.crius.sh/reference/tokens/batch-token-profiles/) [GET`/token/{address}/security`](https://docs.crius.sh/reference/security/security-checks/) [GET`/prices`](https://docs.crius.sh/reference/tokens/batch-prices/)

## The rows: a token list with filters

Example [Anonymous · no key · 1 credit](https://docs.crius.sh/reference/discovery/token-lists/)

### 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**

```bash
curl "https://api.crius.sh/v1/robinhood/tokens?list=top_volume&window=24h&clean=true&min_liquidity_usd=10000&limit=3"
```

**JavaScript**

```js
const res = await fetch("https://api.crius.sh/v1/robinhood/tokens?list=top_volume&window=24h&clean=true&min_liquidity_usd=10000&limit=3");
const { data, meta } = await res.json();
```

**Python**

```python
import requests

r = requests.get(
    "https://api.crius.sh/v1/robinhood/tokens?list=top_volume&window=24h&clean=true&min_liquidity_usd=10000&limit=3",
)
body = r.json()
print(body["data"])
```

**Real response 200 Captured Oct 28, 2026 · 281 lines (arrays trimmed)**

```json
{
  "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](https://docs.crius.sh/reference/discovery/token-lists/) Runs from your browser on the anonymous plan [Data looks wrong? Report it](mailto:support@crius.sh?subject=Wrong%20data%20in%20docs%20example%3A%20Top%2024h%20volume%2C%20clean%20tokens%20with%20%2410k%2B%20liquidity&body=Page%3A%20https%3A%2F%2Fdocs.crius.sh%2Fguides%2Fbuild-a-token-screener%2F%23rows%0ARequest%3A%20GET%20%2Fv1%2Frobinhood%2Ftokens%3Flist%3Dtop_volume%26window%3D24h%26clean%3Dtrue%26min_liquidity_usd%3D10000%26limit%3D3%0A%0AWhat's%20wrong%20or%20missing%3F%0A%0A)

## The extra columns: batch profiles

Example [Anonymous · no key · 1 credit](https://docs.crius.sh/reference/tokens/batch-token-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**

```bash
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"]}'
```

**JavaScript**

```js
const res = await fetch("https://api.crius.sh/v1/robinhood/tokens/batch", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    "tokens": [
      "0x7b8892cea949bc60d494681d1bc3ea96071ab0b8",
      "0x0bd7d308f8e1639fab988df18a8011f41eacad73"
    ],
    "fields": [
      "symbol",
      "price.usd",
      "price.change_pct.24h",
      "market.market_cap_usd",
      "market.liquidity_usd"
    ]
  }),
});
const { data, meta } = await res.json();
```

**Python**

```python
import requests

r = requests.post(
    "https://api.crius.sh/v1/robinhood/tokens/batch",
    json={"tokens":["0x7b8892cea949bc60d494681d1bc3ea96071ab0b8","0x0bd7d308f8e1639fab988df18a8011f41eacad73"],"fields":["symbol","price.usd","price.change_pct.24h","market.market_cap_usd","market.liquidity_usd"]},
)
body = r.json()
print(body["data"])
```

**Real response 200 Captured Oct 28, 2026 · 40 lines (arrays trimmed)**

```json
{
  "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](https://docs.crius.sh/reference/tokens/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.

[Batch token profiles reference](https://docs.crius.sh/reference/tokens/batch-token-profiles/) [Data looks wrong? Report it](mailto:support@crius.sh?subject=Wrong%20data%20in%20docs%20example%3A%20Five%20fields%20for%20two%20tokens%20in%20one%20call&body=Page%3A%20https%3A%2F%2Fdocs.crius.sh%2Fguides%2Fbuild-a-token-screener%2F%23batch%0ARequest%3A%20POST%20%2Fv1%2Frobinhood%2Ftokens%2Fbatch%0A%0AWhat's%20wrong%20or%20missing%3F%0A%0A)

## 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.

```js
// 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](https://docs.crius.sh/plans-and-limits/).

Market cap alone is easy to fake

In the [market cap list](https://docs.crius.sh/reference/discovery/token-lists/) sample, MJDQ (`0x124597e2d8bbe472825c4918a36de246ed0c92f7`) shows a market cap of about $1 billion with 4 holders and $3,795 of liquidity. Rank by volume or liquidity, set a `min_liquidity_usd`, and show the security risk next to the market cap. The [market cap guide](https://docs.crius.sh/guides/market-cap-fdv-and-supply/) explains how market cap and FDV are computed.

## 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](https://docs.crius.sh/reference/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.

## Related

[Use case **Token screeners** Build a Robinhood Chain token screener with the Crius API: new, trending and graduating lists, batch profiles, candles and risk badges, with credit costs. Read](https://docs.crius.sh/use-cases/token-screeners/) [Guide **Find trending tokens** Get the Robinhood Chain tokens with the most real traders, plus top gainers and volume leaders with rugs and wash trading filtered out. Real API responses. Read](https://docs.crius.sh/guides/find-trending-tokens/) [Guide **Find tokens by market cap** Rank Robinhood Chain tokens by market cap, filter by a minimum or a range, and drop fake market caps from dust pools and insider launches. Real responses. Read](https://docs.crius.sh/guides/find-tokens-by-market-cap/) [Guide **Prices for many tokens** Price up to 100 Robinhood Chain tokens in one request with the batch prices API, or get chosen profile fields for many tokens. Real responses, no key needed. Read](https://docs.crius.sh/guides/get-prices-for-many-tokens/) [Guide **Token security score** Score any Robinhood Chain token from 0 to 100 with one API call. Honeypot, buy and sell taxes, owner powers, LP locks, v4 hook risk and insider launches, with real examples. Read](https://docs.crius.sh/guides/token-security-score/) [API reference **Token lists API** Lists: new, bonding, graduating, graduated, trending, top\_volume, top\_gainers, top\_losers, top\_market\_cap (tokens traded in the last 30 days by market cap; prices from thin markets don't rank). Read](https://docs.crius.sh/reference/discovery/token-lists/)
