Quick answer
Dexscreener's public API is keyless, unthrottled at the fingerprint level, and byte-identical on every browser impersonation we tried — but its /latest/dex/search endpoint caps at about 30 results with no cursor, no offset, and no page parameter. It is a lookup API wearing a search API's clothes. Anyone building a crawler against it will silently under-report and never see an error. The correct product shape is a batch lookup across all three endpoints — free-text search, token-address (chunked), and exact chain+pair — which is what we shipped for $4.20 per 1,000 trading-pair rows. And one field, priceUsd, must never be parsed as a float.
Why would a search endpoint that returns 200 be the dangerous case?
Because a week before we built this, we shelved a finished scraper for exactly that.
Avito's listings endpoint returned 200, real JSON, real records with real prices — everything a reach probe asks for. It also ignored its own search parameter and handed back the same fallback feed no matter what you asked it for. We found that out after the Actor was built, and it cost the whole build slot. A search endpoint that quietly ignores your query doesn't fail. It succeeds, forever, at answering a question you didn't ask.
So Dexscreener got the check Avito taught us to run, before a line of code was written:
-
q=pepeversusq=doge→ zeropairAddressoverlap between the two 30-item result sets. -
q=doge's top fivebaseToken.symbolvalues are all literallyDOGE. -
q=zzznonsensetoken123→ zero pairs, genuinely empty rather than a generic feed.
All three pass. The filter is real.
A 200 with real-looking records proves the endpoint answers. It does not prove the endpoint answered you. Query two different things and diff the ids — if they overlap, you have a fallback feed, not a search.
What does the 30-result cap actually break?
The instinct when a JSON API returns 30 items is to look for page 2. There isn't one. No cursor, no offset, no page, no after, no limit — GET https://api.dexscreener.com/latest/dex/search?q=pepe returns 200, application/json; charset=utf-8, about 31 KB, thirty pairs, and that is the entire surface.
That reads like a limitation until you notice what the endpoint is for. Dexscreener isn't a catalogue you walk; it's an index you interrogate. Depth here comes from the size of the batch you send, not from pages you pull. So the Actor batches all three endpoints in one run:
-
/latest/dex/search?q=— free-text, up to 50 queries per run -
/latest/dex/tokens/{addr1,addr2,...}— token contract addresses, up to 300 per run, chunked internally -
/latest/dex/pairs/{chainId}/{pairAddress}— exact pair lookups, up to 200 per run
A search can surface many pairs. A token address returns every pair that token trades in. A pair lookup returns at most one. Every pair returned is one row.
How many addresses fit in one /tokens call?
We tested the boundary instead of trusting the round number. A 30-address call and a 31-address call both returned 200 with an identical response shape and the pairs key present — so Dexscreener enforces no observable ceiling right there.
We chunk at 30 anyway, and the reasoning is worth stating because it generalises: get_tokens() issues exactly one HTTP call per chunk, so a wrong chunk constant changes the call count and never the correctness of the output. When a guessed constant can only cost you requests, guess conservatively and move on. When it can cost you rows, go and measure it. Knowing which kind you're holding is most of the work.
Why is price_usd a string in the output?
Because a memecoin priced at 0.000000001234 does not survive a round trip through a 64-bit float, and the row that comes out the other side is wrong in a way nothing downstream will flag.
Dexscreener sends priceUsd as a JSON string. We keep it as a string, byte for byte, all the way into your dataset. Volume, liquidity, FDV and market cap are floats because they're large-magnitude values where that's the right representation — but the price field, the one number a trading bot actually branches on, is handed to you exactly as the exchange reported it.
If a value's precision is load-bearing and the upstream API sends it as a string, the API already made the correct decision. Parsing it "properly" into a float is the bug.
What do you get back?
One row per trading pair: chain_id, pair_address, dex_id, base_token_address, base_token_symbol, quote_token_symbol, price_usd, volume_h24, liquidity_usd, fdv, market_cap, price_change_h24, txns_buys_h24, txns_sells_h24, plus the source_query that produced the row and an ISO-8601 fetched_at timestamp.
That source_query field matters more than it looks: when you batch 300 addresses into one run, it's the column that tells you which input produced which row — and which inputs produced nothing.
What happens when one lookup in a batch of 300 fails?
The other 299 still ship.
Every query, chunk and pair is fault-isolated: a transport error that survives all five retries is logged with its chunk size and skipped, and the run continues. Retries use exponential backoff on 408 / 429 / 5xx, honour Retry-After, and rotate the browser impersonation on every attempt.
A no-match is not a failure and is not billed. If you look up an address that trades nowhere, that's zero rows, counted in the run summary, charged nothing. The single most common way a scraper quietly robs you is letting one recoverable error kill a run you already paid the start fee for.
Is this target defended?
Not today, and we measured rather than assumed. The API answered identically on both chrome131 and firefox133 impersonations, with no auth, no cookies, and no proxy — same byte count, same shape. So it ships with proxying off by default rather than burning residential bandwidth you don't need.
That's a measured default, not a permanent one. The retry-with-backoff, the fingerprint rotation and the per-item fault isolation are wired in regardless, and turning the proxy on is a one-field change in the input — no code change, no republish — the day that stops being true.
Pricing
Pay-Per-Event: $0.20 per run (a flat start charge, fired once) + $0.004 per trading-pair row written to your dataset. 1,000 rows run about $4.20. No subscription, no minimum — Apify hands every new account $5 of free trial credit.
FAQ
Do I need a Dexscreener API key or account?
No. All three endpoints are public and keyless. No login, no cookie, no header.
Which chains does it cover?
Whatever Dexscreener indexes — Solana, Ethereum, Base, BSC, Arbitrum and the rest. The chain_id comes back on every row; you don't configure it for search or token lookups, only for exact pair lookups.
Why did my search return fewer than 30 rows?
Because that's what the endpoint had. The cap is a ceiling, not a quota — a narrow query legitimately returns less, and a nonsense query returns nothing at all.
Am I charged for addresses that match no pairs?
No. You're charged per row actually returned. Empty searches and no-match addresses are counted in the run summary and billed at zero.
Can I schedule it?
Yes — Apify schedules it like any Actor. A watchlist of token addresses on a 15-minute schedule is the most common shape we see for this one.
😈 Dexscreener Scraper batch-resolves on-chain pair data — price, liquidity, volume, FDV, market cap, buy/sell counts — across search queries, token addresses and exact pairs in a single run. $4.20 per 1,000 rows, pay only for pairs that come back, no card required to try.
No captcha on this one. Just a search endpoint with no page 2, and a price field that a float would quietly ruin. We checked both. 😈
Top comments (0)