---
title: "Filtered token directory (PRO+)"
method: GET
path: "/tokens"
tags: ["Tokens"]
---

# Filtered token directory (PRO+)

`GET /tokens`

## Request

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

Browse tokens by market-cap band, liquidity floor, recent-activity window, primary DEX, authority/safety flags, computed 1h volume, MEV-share ceiling, and MC change. Momentum sorts surface movers (`mc_change_5m_desc`, `mc_change_1h_desc`, `volume_1h_desc`) and a composite `trending` board. Complements `/token/{mint}` (single lookup), `/tokens/almost-bonded` (pre-bond pump.fun), and the KOL-scoped `/kol/tokens/{hot,trending}`.

**Default `min_liq` is 2000 USD** to filter the long tail of phantom-MC tokens from low-liquidity pools. Pass `min_liq=0` to opt out.

**Default `max_mc` is 100,000,000,000 USD ($100B)** — a sanity ceiling that drops phantom market caps (a genuine ~1e12 token supply trading near $1, or a thin-pool price blow-up). No real tracked memecoin approaches it. Pass a higher explicit `max_mc` to opt out.

**Momentum sorts + filters are DB-native** (via the `token_prices_trending` view): `min_volume_1h_usd`, `max_mev_share_pct`, `min_liq_mc_ratio`, `max_liq_mc_ratio`, `mc_change_1h_min_pct`, `mc_change_1h_max_pct` and the momentum sorts paginate correctly with no over-fetch. The `trending` sort = recent-volume × positive-momentum composite, recency-bounded to the last 1h, and echoes `filters.trending_version: "v1"`.

**Only `deployer_tier` and `launchpad`** remain post-filters (joined from `deployer_tokens`, not the view). They are served by a bounded SCAN: candidates are walked in pages until `limit` matches, the candidates run out, or the scan budget is spent. `has_more:false` only when the candidates ran out; `pagination.scan_truncated:true` = the budget ran out first and more matches MAY exist — continue with `offset = pagination.next_offset` (a RAW candidate offset, not a match count). Offset pages walk a LIVE order (`pagination.order_is_live: true`): MC and activity move between calls, so rows can shift across pages — treat the screener as a ranking, not a ledger.

**LP burn (2026-09-21):** `lp_burned` / `lp_burn_status` come from LP-custody evidence only (`lp_burnt_pct`). No Solana writer populates it yet, so `lp_burn_status` is `unknown` and `lp_burned` null for every token, and `lp_burned=true|false` currently matches nothing. `lp_burned` is DEPRECATED: `true` = verified burned/locked, `false` = verified NOT burned, and a token with unknown LP custody matches neither (false never turns unknown into "unburned"); a request that sends it gets a `deprecations` entry. The old `lp_burned` flag was the token-SUPPLY burn — filter that with `supply_burn` and read it as `token_supply_burn_detected`.

## Query parameters

- `min_mc` number
- `max_mc` number
- `min_liq` number
- `active_h` number
- `primary_dex` 'pumpfun' | 'pumpswap' | 'raydium' | 'meteora' | 'orca' | 'raydium_clmm' | 'launchlab'
- `deployer_tier` 'elite' | 'good' | 'moderate' | 'rising' | 'cold' | 'unranked'
- `launchpad` 'pumpfun' | 'launchlab' | 'bags'
- `authority_revoked` boolean
- `exclude_token2022` boolean
- `lp_burned` boolean
- `supply_burn` boolean
- `min_volume_1h_usd` number
- `max_mev_share_pct` number
- `min_liq_mc_ratio` number
- `max_liq_mc_ratio` number
- `mc_change_1h_min_pct` number
- `mc_change_1h_max_pct` number
- `sort` 'mc_desc' | 'mc_asc' | 'last_trade_desc' | 'liquidity_desc' | 'cumulative_volume_desc' | 'mc_change_5m_desc' | 'mc_change_1h_desc' | 'volume_1h_desc' | 'trending'
- `limit` integer
- `offset` integer

## Response `200`

OK. Returns `tokens[]` (with `liquidity_to_mc_ratio`, `deployer_tier`, `launchpad` (`pumpfun`/`launchlab`/`bags`, null when unknown), `mev_share_pct`, `organic_volume_1h_usd`, `mc_change_5m_pct`, `mc_change_1h_pct`, `is_token_2022`, `authorities_revoked`, etc.), `pagination` (with `post_filtered` flag), and a `filters` echo (with `min_liq_default_applied`; `trending_version: "v1"` when `sort=trending`).

- object
  - `tokens` object[]
    - `mint` string
    - `symbol` string, nullable
    - `name` string, nullable
    - `price_usd` number, nullable
    - `market_cap_usd` number, nullable
    - `fdv_usd` number, nullable
    - `liquidity_usd` number, nullable
    - `liquidity_to_mc_ratio` number, nullable
    - `primary_dex` string, nullable
    - `launchpad` 'pumpfun' | 'launchlab' | 'bags' | 'null', nullable
    - `deployer_tier` string, nullable
    - `authorities_revoked` boolean
    - `lp_burned` boolean, nullable: From LP evidence ONLY: true iff lp_burn_status = verified, false iff not_verified, null = unknown (every token today). Never derived from a token-supply burn.
    - `lp_burn_status` 'verified' | 'not_verified' | 'unknown': LP burn/lock evidence: `verified` only with measured LP-token custody (lp_burnt_pct ≥ 99). No Solana writer populates LP custody yet, so today every token is `unknown`. A token-supply burn is never promoted to an LP claim.
    - `lp_burnt_pct` number, nullable: Measured share of LP tokens burned (0–100); null = not measured
    - `lp_secured_pct` number, nullable: Burned + non-cancelable LOCKED share of the LP (0–100), from LP evidence of the primary constant-product pool (pool ledger vs LP mint supply, incinerator, decoded LP locks incl. permanent Raydium LP-Lock / Meteora escrows). Sender-cancelable and expired locks are not counted. null = unknown (no evidence, concentrated/position-NFT pool, or not yet measured), never 0 for unknown. lp_burnt_pct stays burn-only.
    - `lp_secured_basis` 'permanent' | 'temporary' | 'mixed' | 'null', nullable: permanent = burned and/or permanently locked LP only; temporary = only time-limited locks; mixed = both; null = nothing secured or unknown.
    - `lp_locked_until` string, date-time, nullable: Earliest end of a counted TEMPORARY LP lock; null when no temporary lock counts. Permanent locks carry no date.
    - `token_supply_burn_detected` boolean, nullable: The mint's on-chain supply decreased (token burn) — the flag lp_burned used to expose. Not LP evidence.
    - `is_token_2022` boolean
    - `last_trade_time` string, date-time, nullable
    - `mc_change_5m_pct` number, nullable
    - `mc_change_1h_pct` number, nullable
    - `organic_volume_1h_usd` number, nullable
    - `mev_share_pct` number, nullable
  - `pagination` object
    - `limit` integer
    - `offset` integer
    - `returned` integer
    - `has_more` boolean: false ONLY when the candidates ran out — never inferred from a short post-filtered page
    - `next_offset` integer, nullable: Raw candidate offset to resume from (pass as offset); null when exhausted
    - `order_is_live` true: Offset pages walk a live order (MC/activity move between calls), not a snapshot
    - `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
    - `scanned_until_offset` integer: offset + scanned (post-filtered pages)
  - `filters` object: Echo of applied filters, including defaults
    - `min_mc` number, nullable
    - `max_mc` number
    - `max_mc_default_applied` boolean
    - `min_liq` number
    - `min_liq_default_applied` boolean
    - `active_h` number, nullable
    - `primary_dex` string, nullable
    - `launchpad` string, nullable
    - `deployer_tier` string, nullable
    - `authority_revoked` boolean, nullable
    - `exclude_token2022` boolean, nullable
    - `lp_burned` boolean, nullable
    - `lp_burned_source` string: What lp_burned=true and lp_burned=false each filter on (verified LP evidence only; unknown matches neither)
    - `supply_burn` boolean, nullable
    - `min_volume_1h_usd` number, nullable
    - `max_mev_share_pct` number, nullable
    - `min_liq_mc_ratio` number, nullable
    - `max_liq_mc_ratio` number, nullable
    - `mc_change_1h_min_pct` number, nullable
    - `mc_change_1h_max_pct` number, nullable
    - `sort` string
    - `trending_version` string, nullable: "v1" only when sort=trending
  - `deprecations` object[]: Present only when a deprecated parameter was sent (today: `lp_burned`). Each entry names the parameter, exactly what it matches, and its replacement.
    - `param` string
    - `status` 'deprecated'
    - `matches` string
    - `replacement` string

## Other responses

- `400`: Invalid query parameters
- `401`: Missing or invalid API key, or an expired free-tier key. Authentication-stage error: no `X-RateLimit-*` headers, no `_rid`.
- `403`: Forbidden. One of: the key's tier is below this endpoint's or parameter's gate (`tier_required` with an `upgrade` block); the public demo key used outside its allow-list (`demo_key_endpoint_not_allowed`); an invalid or revoked key; an origin-restricted key called from a disallowed origin (`code` `origin_not_allowed` / `origin_required`). `X-RateLimit-*` headers are present only on `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`: The API-key or subscription lookup could not be answered (authentication stage). Retryable and not your fault: it is never counted as a failed authentication. Honour `Retry-After`; no `X-RateLimit-*`, no `_rid`.

---

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