Key takeawaysThere is no new-pairs endpoint, and no route accepts a "created after" filter.
- There is no new-pairs endpoint, and no route accepts a "created after" filter.
- The profile schema promises no timestamp — "new" is whatever your client hasn't seen before.
pairCreatedAtis a nullable field on the Pair object: you filter on it locally, never query by it.- Limits come in two tiers: 60 requests a minute for discovery feeds, 300 for pair and token lookups.
- Key your state on
chainId:pairAddress— one token can gain new pools later. - DexScreener is an indexer with no published detection latency; being first to a launch needs an on-chain source.
This summary was created with AI.
The endpoint everyone looks for
Start with the full documented surface, because that's what settles the question. The public reference at api.dexscreener.com lists these routes and nothing else:
| Route | Limit |
|---|---|
/token-profiles/latest/v1 |
60 req/min |
/token-profiles/recent-updates/v1 |
60 req/min |
/community-takeovers/latest/v1 |
60 req/min |
/ads/latest/v1 |
60 req/min |
/token-boosts/latest/v1, /token-boosts/top/v1 |
60 req/min |
/orders/v1/{chainId}/{tokenAddress} |
60 req/min |
/metas/trending/v1, /metas/meta/v1/{slug} |
60 req/min |
/latest/dex/pairs/{chainId}/{pairId} |
300 req/min |
/latest/dex/search |
300 req/min |
/token-pairs/v1/{chainId}/{tokenAddress} |
300 req/min |
/tokens/v1/{chainId}/{tokenAddresses} |
300 req/min |
There is no "new pairs" route in that list, and no route that accepts a "created after" filter. The pairs endpoint documentation describes lookups: /latest/dex/pairs/{chainId}/{pairId} returns one specific pair you already know the address of. The latest dex pairs endpoint people search for — something that hands you everything created in the last ten minutes — doesn't exist in the public API. What the site shows on its new-pairs tab is not exposed as a documented route.
This is also why the search demand exists in the first place. The website's New Pairs screener is far richer than the documented API: it filters the entire indexed pair universe by age, chain, liquidity, volume, launchpad and profile status, and sorts by age directly. No documented REST query exposes that universe. The public API only lets you retrieve pairs once you already hold a token or pair candidate — which is a completely different starting point.
Keeping three tasks separate saves most of the confusion around this topic:
| Goal | Where it actually comes from |
|---|---|
| Every newly created pool, as it happens | On-chain RPC, log subscriptions, or a launchpad's own feed |
| New or recently updated token profiles | /token-profiles/latest/v1 and /token-profiles/recent-updates/v1 |
| Age of a pair you already discovered | pairCreatedAt on the Pair object |
Two more things worth noting while you're looking at that table. Every call is a plain GET with no key and no authentication. And the limits split into two tiers — 60 per minute for the discovery-ish routes, 300 for the pair and token lookups — which turns out to shape every design decision below, because the endpoints you'd use to find tokens are the scarce ones and the endpoints you'd use to enrich them are five times more generous.
What token-profiles and token-boosts actually return
The latest token profiles route is the closest thing to a discovery feed, and its schema is smaller than people expect: url, chainId, tokenAddress, icon, header, description, and a links array. That's it.
Read that list again and note what the schema doesn't promise: no timestamp. Nothing in the documented contract lets you sort or filter profiles by age, and the word "latest" describes the ordering the endpoint applies rather than a field you can act on.
In practice the live endpoint currently returns more than the schema lists — an updatedAt timestamp alongside fields like cto and openGraph. Useful, and worth knowing about, with two caveats that matter more than the convenience. It is not a pair-creation time — it reflects profile activity, not when a pool appeared. And it isn't part of the documented contract, so it can change or disappear without a version bump. Read it if it helps your diffing, but don't let your pipeline depend on it, and never substitute it for pairCreatedAt.
The same shape applies to community takeovers and to boosts — boosts add promotion data, not creation data.
That has one direct consequence for how you build: the state lives in your client, not in the API. You poll, you diff the returned set against what you already stored, and anything unseen is your "new". Miss a poll and you may miss entries entirely, because there's no cursor to resume from.
And these feeds are selective, though not in the way it's often described. Tokens are listed on DexScreener automatically, and profile metadata can be pulled from external token lists; the paid enhanced-info product is a way to supply or update that information faster and on your own terms. Boosts are a straightforwardly purchased promotion product. So the accurate framing is that this is a feed of profile and metadata activity, not a firehose of every pool that touched a DEX in the last minute. For a scanner that's sometimes exactly what you want, and sometimes precisely what you don't.
The latest dex tokens endpoint that older tutorials cite — /latest/dex/tokens/{tokenAddresses} — belongs to an earlier generation of this API. The current reference documents /tokens/v1/{chainId}/{tokenAddresses} instead, and the latest dex tokens documentation you'll find in blog posts written a year ago points at the legacy path. Use the versioned route.
Filtering on pairCreatedAt after the fact
Here's where the actual answer lives. The pairCreatedAt field is part of the Pair object — the shape returned by the pair and token lookup routes — and in the schema it's an integer, nullable. It's a Unix timestamp in milliseconds, and being nullable matters: some pairs come back without it, so code that assumes the field is present will throw on the ones that aren't.
Crucially, it's a response field, not a query parameter. There is no pairCreatedAt_gt you can send. The working pattern is therefore two-stage: find candidate token addresses on the 60-per-minute feeds, then look up their pairs on the 300-per-minute routes and filter locally on age.
import time, requests
BASE = "https://api.dexscreener.com"
HEAD = {"Accept": "application/json"}
seen_pairs: set[str] = set() # keyed by pair, not by token — see note below
def latest_profiles():
r = requests.get(f"{BASE}/token-profiles/latest/v1", headers=HEAD, timeout=15)
r.raise_for_status()
data = r.json()
return data if isinstance(data, list) else [data] # tolerate both shapes
def pairs_for_token(chain_id, token_address): # 300 req/min tier
r = requests.get(f"{BASE}/token-pairs/v1/{chain_id}/{token_address}",
headers=HEAD, timeout=15)
r.raise_for_status()
return r.json() or []
def fresh_pairs(max_age_minutes=60):
cutoff_ms = (time.time() - max_age_minutes * 60) * 1000
out = []
for profile in latest_profiles(): # candidates, re-checked every cycle
for pair in pairs_for_token(profile["chainId"], profile["tokenAddress"]):
key = f"{pair['chainId']}:{pair['pairAddress']}"
if key in seen_pairs: # your store is the only cursor
continue
created = pair.get("pairCreatedAt") # nullable — never assume
if created and created >= cutoff_ms:
seen_pairs.add(key)
out.append(pair)
return out
for p in fresh_pairs():
age_min = (time.time() * 1000 - p["pairCreatedAt"]) / 60000
print(f"{p['chainId']:<10} {p['baseToken']['symbol']:<12} {age_min:6.1f} min {p['url']}")
What to look at, and this is the detail most implementations get wrong: state is keyed on the pair, not the token. One token can have many pools — that's exactly why /token-pairs/v1/{chainId}/{tokenAddress} returns a list — so if you mark a token as seen, a new pool created for it tomorrow will never be reported. Keying on chainId:pairAddress and re-enriching candidates every cycle fixes that. pairCreatedAt is read with .get and checked for truthiness because it's nullable in the schema. And the two tiers are respected by design: one call per cycle to the 60-per-minute feed, then the cheaper 300-per-minute route for enrichment.
What this deliberately doesn't do: no persistence, so a restart re-reports everything (put seen_pairs in Redis or a file); no rate-limit backoff; no candidate-list ageing, so the token set only grows; no handling for a token with many pools where you only care about the deepest. And the standing caveat — a token enters this loop only if it has a profile, so even a correct pair-keyed detector sees a subset of new pairs, not all of them.
Narrowing to a single chain
Most of the search volume around this topic is about one chain, usually Solana, and the API's answer to "new pairs on Solana" is again indirect. The discovery feeds take no chain parameter at all, so you filter on the chainId field after the response arrives — one line in the loop above.
The routes that take a chain as a path parameter are the lookup ones: /token-pairs/v1/{chainId}/{tokenAddress} and /tokens/v1/{chainId}/{tokenAddresses} for tokens you already have, and /latest/dex/pairs/{chainId}/{pairId} for a specific pair. That's the extent of latest dex pairs by chain in the documented API — chain scoping for lookups, not for discovery.
Two more angles worth knowing. /latest/dex/search sits in the 300-per-minute tier and accepts a free-text query, which makes it useful for enrichment and for finding pairs by ticker or address, though it isn't a chain-filtered new-pair feed either. And a route that's easy to miss: /metas/meta/v1/{slug} returns full Pair objects in bulk for a trend slug — so if your interest is thematic rather than exhaustive, it's a documented way to get many pairs, with their pairCreatedAt values, in one 60-per-minute call.
The honest limitation is architectural rather than numeric. DexScreener is an indexer, not a chain-native event source: it lists tokens automatically once a pool exists and has traded, and processes chain logs on its own schedule. It publishes no detection-latency guarantee, so don't assume a deterministic figure in either direction — and don't take a number from a blog post as one either. If seconds matter to your strategy, detect pool creation on-chain and use DexScreener to enrich what your own stack already found.
Streaming instead of polling
The natural next question is whether you can subscribe instead of poll, and the answer is yes — on a page separate from the REST reference. DexScreener documents a WebSocket API at wss://api.dexscreener.com whose paths mirror the REST feeds: /token-profiles/latest/v1, /token-profiles/recent-updates/v1, /token-boosts/latest/v1, /token-boosts/top/v1, /community-takeovers/latest/v1, /ads/latest/v1.
Two things to know before you switch. The payload is wrapped — the server sends {"limit": 90, "data": [ ... ]} rather than the bare array REST returns, so a client written against REST will break on the extra layer. And the streams cover exactly the profile and boost feeds, not pair creation: subscribing gets you profile activity as it happens, which removes the diffing problem in the loop above, but it still doesn't hand you every new pool.
Streaming helps most where polling is weakest: an entry that appears and disappears between two polls is invisible to a poller and visible to a subscriber. What it doesn't change is the enrichment side — you still spend the 300-per-minute tier turning candidates into pairs with liquidity, volume and creation times. And you still keep a persistent store, because the WebSocket documentation covers the connection and payload but not reconnect semantics: treat a dropped connection as a state gap, take the handshake payload as your new baseline, and reconcile against what you already have.
Where this becomes an infrastructure question is scale: several chains, several strategies, and a watchlist that grows. Be careful about one assumption here, though. The reference states 60 and 300 requests per minute per endpoint, but it doesn't define the limiter's scope — it doesn't say the budget is counted per IP address, doesn't promise an independent allowance to each address, and doesn't describe a burst model. So the correct order is: measure the scope against live behaviour first, and only then decide whether separate workers can genuinely run independent budgets. For what those two tiers mean in practice, see our DexScreener rate-limit guide. If your measurements say they can, our proxies for crypto projects provide dedicated IPv4 addresses, static for the full plan term, one per worker — and check DexScreener's terms for how they expect the API to be used before scaling a polling fleet.