---
title: "Robinhood Chain token discovery (PRO+)"
method: GET
path: "/rhc/tokens"
tags: ["Robinhood Chain"]
---

# Robinhood Chain token discovery (PRO+)

`GET /rhc/tokens`

## Request

- Base URL: `https://madeonsol.com/api/v1`
- URL: `https://madeonsol.com/api/v1/rhc/tokens`
- Auth: HTTP bearer

Live-priced Robinhood Chain tokens with market cap, liquidity, peak MC + drawdown, launchpad, and deployer reputation tier. Sortable and filterable. Filters the candidate query cannot express are served by a bounded scan (`scan` block) — a short or empty page never implies exhaustion unless has_more is false. Tier: **PRO+**.

## Query parameters

- `limit` integer
- `sort` 'last_trade' | 'market_cap' | 'liquidity' | 'peak_mc' | 'newest' | 'oldest'
- `since` string, date-time
- `after` string, date-time
- `cursor` string
- `has_pool` 'true' | 'false'
- `asset_class` 'equity' | 'other'
- `min_mc_usd` number
- `min_liquidity_usd` number
- `launchpad` string
- `max_age_days` integer

## Response `200`

OK.

- object
  - `chain` 'robinhood'
  - `tokens` object[]
    - `token_address` string
    - `symbol` string, nullable
    - `name` string, nullable
    - `decimals` integer, nullable: From rhc_tokens. Added 2026-09-08 — needed to parse on-chain raw amounts correctly.
    - `asset_class` 'equity', nullable: `equity` = beacon-verified Robinhood tokenized stock/ETF (issuer beacon read from our node — never the name); null = everything else.
    - `first_seen_at` string, date-time, nullable: When OUR node first saw the token trade or get a pool — not the contract creation block.
    - `launchpad` string, nullable
    - `is_graduated` boolean, nullable
    - `deployer_address` string, nullable
    - `deployer_tier` 'elite' | 'good' | 'neutral' | 'spammer', nullable
    - `price_usd` number, nullable
    - `market_cap_usd` number, nullable
    - `fdv_usd` number, nullable
    - `peak_mc_usd` number, nullable
    - `peak_mc_at` string, date-time, nullable
    - `drawdown_from_peak_pct` integer, nullable: Percent below all-time-high MC.
    - `liquidity_usd` number, nullable
    - `liquidity_basis` 'v4_virtual_ceiling' | 'measured': How liquidity_usd was derived. uniswap-v4 has no per-pool balances, so its figure is virtual reserves clamped to the PoolManager's total holdings — a provable CEILING, not measured per-pool TVL. All other venues are measured from real balances.
    - `primary_dex` string, nullable
    - `primary_pool` string, nullable
    - `last_trade_time` string, date-time, nullable
  - `count` integer
  - `sort` string
  - `since` string, date-time, nullable: sort=newest only — echoes the request's since=.
  - `next_since` string, date-time, nullable: sort=newest only. Feed back as since= to poll for newer launches.
  - `note` string: sort=newest / sort=oldest only — constant explanation of the first_seen_at semantics and the cursor contract.
  - `after` string, date-time, nullable: sort=oldest only — echoes the request's after=.
  - `has_more` boolean: sort=oldest only — false ONLY when the candidates ran out (never inferred from a short filtered page).
  - `cursor` string, nullable: sort=oldest only — echoes the request's cursor=.
  - `next_cursor` string, nullable: sort=oldest only (PREFERRED). Feed back as cursor= — strict (first_seen_at, address) keyset; null = walk complete.
  - `next_after` string, date-time, nullable: sort=oldest only, LEGACY inclusive cursor. Feed back as after= (dedupe on token_address). When a scanned page matched nothing it advances to the last scanned row.
  - `scan` object: Present when a filter (launchpad / has_pool / price criteria) had to be scanned after the candidate fetch (audit F09): has_more is then false only when the candidates ran out, and scan_truncated=true means the per-request budget ran out first — more matches MAY exist.
    - `post_filtered` boolean
    - `scanned` integer: Candidate rows examined
    - `scan_truncated` boolean: The per-request scan budget ran out before `limit` matches — more matches MAY exist past the cursor/offset
    - `scan_budget` integer

## Other responses

- `401`: Missing or invalid API key, or an expired free-tier key. Authentication-stage error: no `X-RateLimit-*` headers, no `_rid`.
- `403`: PRO+ tier required.
- `429`: Too many requests. Every 429 carries `Retry-After`. A quota 429 (`rate_limit_exceeded`, with `X-RateLimit-*`), a heavy-query concurrency 429 (`error_kind` `concurrency`/`capacity`), or the per-IP authentication-failure block (30 failed auths/min; plain `error` + `retry_after_seconds`, no `X-RateLimit-*`).
- `500`: Unexpected server error from the route (`Internal server error`, with `_rid`). Safe to retry with backoff. A failed API-key lookup is a 503 `auth_backend_unavailable`, never a 500.
- `503`: Temporarily unavailable — a Postgres statement timeout while the dataset is under load or maintenance (`statement_timeout`, with `X-RateLimit-*` and `_rid`), or the API-key lookup itself unavailable (`auth_backend_unavailable`, authentication stage: no `X-RateLimit-*`, no `_rid`, not counted as a failed authentication). Always carries `Retry-After`; honour it.

---

[API](https://skmtc.dev/madeonsol/apis/madeonsol-robinhood-chain-api.md) · [All operations](https://skmtc.dev/madeonsol/apis/madeonsol-robinhood-chain-api/llms.txt) · [OpenAPI document](https://skmtc.dev/madeonsol/apis/madeonsol-robinhood-chain-api/revisions/9ea9a35ee2bf?raw)
