Every Solana memecoin trader ends up wanting the same thing eventually: a scanner that filters the firehose down to their specific rules — not GMGN's defaults, not Photon's "trending" sort, not Bullx's hot tab. You want mc between $100K and $2M, liquidity above $5K, on PumpSwap, less than 25% MEV share, gained at least 30% in the last hour, authorities revoked. Eleven filters in one query, exactly that combination.
GET /api/v1/tokens is that endpoint. It exposes 12 server-side filters over the live token_prices state (the same state mc-tracker uses to enrich every KOL trade and webhook event we emit), with five sort options and clean pagination up to 10,000 rows. This post walks through the full filter surface, what each filter is doing under the hood, and an example scanner you can run from a single API call.
The filter surface in one table
| Filter | Type | What it does |
|---|
min_mc / max_mc | USD float | Filter on current market cap (price × on-chain supply). |
min_liq | USD float | Filter on USD-denominated liquidity in the primary pool. Defaults to 2000 — opt out with min_liq=0. |
active_h | hours, 0.1–168 | Drop tokens whose last trade is older than this many hours. |
primary_dex | enum | One of pumpfun, pumpswap, raydium, raydium_clmm, meteora, orca. |
authority_revoked | bool | true requires BOTH mint and freeze authority revoked. false returns tokens with at least one authority live. |
exclude_token2022 | bool | true excludes Token-2022 mints. |
min_lp_burnt_pct | 0–100 | Floor on % of LP tokens burned (LP lock proxy). |
min_volume_1h_usd | USD float | Floor on organic (MEV-stripped) volume in the last hour. |
max_mev_share_pct | 0–100 | Cap on the MEV share of cumulative tracked volume. |
mc_change_1h_min_pct / mc_change_1h_max_pct | percent | Band-pass filter on % MC change over the last hour. |
sort | enum | mc_desc, mc_asc, last_trade_desc, liquidity_desc, cumulative_volume_desc. |
limit / offset | int | Up to 100 per page, up to 10K offset. |
The first eight filters run at the DB layer — efficient. The four after that (min_volume_1h_usd, max_mev_share_pct, mc_change_1h_*) are computed from velocity history and applied in JavaScript after the DB fetch. The route over-fetches 3× when any of those is set so your page size stays roughly stable. Response carries pagination.post_filtered: true so your client knows when it's happening.
Why a min_liq=2000 default
If you look at the long tail of token_prices, there are tens of thousands of tokens with absurd MC values that nobody trades — pools with $50 of liquidity but a stamped supply of one trillion, producing a phantom $5M MC. They're not interesting and they crowd the top of any naive MC sort. The default min_liq=2000 filters them out at the API layer.
For everyone except the most fringe sniper use case, leave the default in place. To explicitly include the phantom tokens (e.g., to audit them or to scan brand-new mints that haven't pooled yet), pass min_liq=0.
Response shape
{
"tokens": [
{
"mint": "4k3Dyj...",
"symbol": "PEPECAT",
"name": "Pepe Cat",
"price_usd": 0.00029,
"market_cap_usd": 287_000,
"fdv_usd": 290_000,
"liquidity_usd": 14_200,
"primary_dex": "pumpswap",
"authorities_revoked": true,
"lp_burnt_pct": 100,
"is_token_2022": false,
"last_trade_time": "2026-05-14T11:42:17Z",
"mc_change_5m_pct": 8.2,
"mc_change_1h_pct": 41.7,
"organic_volume_1h_usd": 81_000,
"mev_share_pct": 18.4
}
],
"pagination": {
"limit": 50,
"offset": 0,
"returned": 28,
"has_more": false,
"post_filtered": true
},
"filters": { "min_mc": 100000, "max_mc": 2000000, "min_liq": 5000, ... }
}
The filters echo is useful when debugging scanners — it tells you what the server actually applied, including the min_liq_default_applied: true flag when you didn't override the default.
Example: a momentum-aware safety scanner
Here's the eleven-filter query from the intro, in a single SDK call:
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: process.env.MADEONSOL_API_KEY! });
async function safeMomentumScanner() {
const { tokens } = await client.tokens.list({
min_mc: 100_000,
max_mc: 2_000_000,
min_liq: 5000,
active_h: 2,
primary_dex: "pumpswap",
authority_revoked: true,
min_lp_burnt_pct: 90,
min_volume_1h_usd: 25_000,
max_mev_share_pct: 25,
mc_change_1h_min_pct: 30,
sort: "mc_change_1h_min_pct" in tokens ? "last_trade_desc" : "mc_desc",
limit: 50,
});
return tokens.map((t) => ({
symbol: t.symbol,
mint: t.mint,
mc: t.market_cap_usd,
pct_1h: t.mc_change_1h_pct,
vol_1h: t.organic_volume_1h_usd,
mev: t.mev_share_pct,
}));
}
In English: "PumpSwap-graduated tokens between $100K and $2M MC, with both authorities revoked, ≥90% LP burned, with at least $25K of clean organic volume in the last hour, MEV share below 25%, gained at least 30% in the last hour, traded in the last 2 hours."
You can run that one call on a 5-second cadence and you have a real scanner. Add a Telegram or Discord webhook on the diff between successive runs and you have a notifier.
Picking the right sort
Five sorts cover the obvious scanner patterns:
mc_desc — "biggest first." Default. Useful when you've banded MC and want the top movers in the band.
mc_asc — "smallest first." Pair with min_mc=50000 to scan fresh micro-caps without the dust tail.
last_trade_desc — "freshest tape." The best sort for entry signals — your top results are tokens that are actually trading right now.
liquidity_desc — "deepest first." For larger size; deepest pools take the best fills.
cumulative_volume_desc — "most-traded since we started tracking." Useful for liveness checks; a token at the top of this list is one with sustained interest, not a one-candle wonder.
There's no mc_change_1h_pct_desc sort, by design — the field is a post-filter, not a DB-indexed column. To get something approximating a pct_1h_desc view, sort by last_trade_desc and apply mc_change_1h_min_pct=N to floor the value; the page is already filtered to fresh-traded high-velocity tokens.
What's NOT in the filter set
Things you might expect to filter on but can't (today):
- Holder count. We don't index holder counts on every mint — there are 100K+ tokens and the holder list is unbounded per token. The
/tokens/{mint}/cap-table endpoint covers early-buyer quality, which is the question holders are usually a proxy for.