WHYUS? · Developers
Everything the screener shows is available as JSON from https://dex.whyus.fun. There is no key, no account and no quota to sign up for: the endpoints are read-only views of the same live market data, and the app's own server is what calls the upstream providers, so a caller never spends their rate limit.
Last updated: 23 October 2026
One origin serves every endpoint. It is this screener's own domain, not the host you happen to be reading this on:
https://dex.whyus.fun/api/market
Every response is application/json with access-control-allow-origin: *, so a browser page, a script or a worker can call it directly with no CORS setup, no key and no signing.
There is nothing to install and nothing to sign up for. One request gets the whole live market, and the shape of the answer is the same whether you read it in a shell, in a browser or in a notebook:
GET /api/market answers every pair the screener knows about, already merged from both providers.updatedAt before you trust a number. The snapshot is shared and refreshed at most every 30 seconds, so that field is what tells you how old the data is — not the moment you asked for it.chainId + pairAddress. A token can trade in several pools and the same address exists on several chains.curl -s "https://dex.whyus.fun/api/market" | jq '{updatedAt, count, top: .pairs[0].baseToken.symbol}'Every sample on this page can be copied with the button in its corner, and the playground below sends a real request so you can see the answer before you wire anything up.
This panel calls whichever endpoint you pick on the deployment serving this page, and prints the status, the timing and the JSON that came back. Requests are plain GETs: the API has no write endpoint at all, so nothing here can change anything.
Send a request to see the response this deployment actually answers with.
The same call from a terminal
curl -s "https://dex.whyus.fun/api/market" | head -c 400
A response is trimmed to the first 4,000 characters so the page stays readable — a full /api/market snapshot is far larger. The endpoint links in the table below open the untrimmed JSON.
| Endpoint | Returns | Cache-Control |
|---|---|---|
GET /api/market | Every pair in the current screener snapshot, merged from both providers | public, max-age=30 |
GET /api/pinned | The listings pinned to the top of the screener, in the order they lead it | public, max-age=30 |
GET /api/boost/packages | The boost catalogue: packages, prices, durations and supported chains | public, max-age=30 |
GET /api/boost/queue | Boost placements, newest first, without buyer contact details | public, max-age=0 |
GET /api/advertise/packages | The advertising catalogue: packages, prices, durations and the categories an advert is filed under | public, max-age=30 |
GET /api/advertise/queue | Advertising placements, newest first, without advertiser contact details | public, max-age=0 |
Each path is relative to the base URL above, and the link opens the live JSON — on the host you are reading this page from, so you can try an endpoint before wiring it up.
GET /api/market
| Field | Type | Notes |
|---|---|---|
updatedAt | string | ISO-8601, when the server's snapshot was taken |
count | number | Length of pairs |
pairs | Pair[] | The merged snapshot — see the pair reference below |
GET /api/pinned
| Field | Type | Notes |
|---|---|---|
count | number | Length of pairs — zero when nothing is pinned |
pairs | Pair[] | The pinned listings in the order they lead the screener, each carrying pinned: true |
pair | Pair | null | The first of pairs, for callers that only want the lead listing |
GET /api/boost/packages
| Field | Type | Notes |
|---|---|---|
currency | string | Always USD; the fee is paid in crypto at the live rate |
packages[] | { id, name, tagline, priceUsd, durationHours, duration, perks } | The catalogue the Boost page sells from |
chains[] | { id, label } | Chains a placement can be bought for |
GET /api/boost/queue
| Field | Type | Notes |
|---|---|---|
count / running | number | Every placement, and how many are live right now |
orders[] | object[] | id, packageId, chainId, tokenAddress, token ({ symbol, name, pair, image }), status, createdAt, expiresAt, running |
orders[].payment | object | absent | Present once the fee was sent on-chain: rail, symbol, amount and the txSignature to check on the explorer |
GET /api/advertise/packages
| Field | Type | Notes |
|---|---|---|
packages[] | { id, name, tagline, priceUsd, durationHours, duration, perks } | Same shape as the boost catalogue — an advert is bought the same way |
categories[] | { id, label, hint } | What an advert can be filed under: product, website, launch, service, event |
GET /api/advertise/queue
| Field | Type | Notes |
|---|---|---|
count / running | number | Every request, and how many adverts are running right now |
orders[] | object[] | id, packageId, chainId, title, url, tagline, image, category, status, createdAt, expiresAt, running |
orders[].payment | object | absent | The same payment shape the boost queue carries |
An advertising request is filed under the chain its fee was paid on, so chainId here is a payment rail rather than a subject — the advert itself belongs to no chain.
| Field | Type | Notes |
|---|---|---|
source | "dexscreener" | "geckoterminal" | Which provider supplied the row |
chainId | string | Provider's chain id — solana, base, pulsechain, … |
dexId | string | The exchange the pool trades on: pumpswap, orca, raydium, uniswap-v3, … |
pairAddress | string | The pool address. With chainId it identifies the row, and opens the same pair page the screener links to |
url | string | The pool on the provider that supplied it |
baseToken / quoteToken | { address, name, symbol } | baseToken.address is the token's contract |
priceUsd / priceNative | string | Decimal strings, because a token can trade at 0.0000000174 USD and a JSON number would lose precision |
txns | { m5, h1, h6, h24 } of { buys, sells } | Trade counts per window |
volume | { m5, h1, h6, h24 } of number | USD volume per window |
priceChange | { m5, h1, h6, h24 } of number | Percent change per window. Those four are the only windows either provider publishes — nothing finer or longer |
liquidity | { usd, base, quote } | The pool's reserve |
fdv / marketCap | number | Present only when the provider publishes them |
pairCreatedAt | number | Pool creation time, epoch milliseconds |
info | { imageUrl, header, websites[], socials[] } | Token imagery and links, when the provider has them |
boosts | { active } | Paid boosts the provider currently shows for the token |
pinned | boolean | Only on a listing WHYUS? has pinned to the top of the screener |
pinLabel | string | Optional chip text on a pinned row, when it was labelled |
priceChange?.h1 can be undefined while priceChange.h24 is there./api/market for a leaderboard view and the providers directly when you need every pool.pinned: true only says WHYUS? leads the list with it. Pinned listings sit ahead of the ranking on every tab, in the panel's own order, and they step aside while a caller searches or filters by another chain.A snapshot of the market, trimmed to a single pair:
curl
curl -s "https://dex.whyus.fun/api/market" | jq '.count, .pairs[0]'
Response, trimmed
{
"updatedAt": "2026-10-23T22:14:05.000Z",
"count": 640,
"pairs": [
{
"chainId": "solana",
"dexId": "pumpswap",
"pairAddress": "EKhCvd4QUEydYrsJQ5euYTQAqCia8NmrF2Vt7UDhErXi",
"baseToken": {
"address": "AdoGKHb4hLJVqfL8NJYFamJKNtmLGXpZWxZDpo3nVgCh",
"symbol": "WHY",
"name": "Whyus.fun"
},
"priceUsd": "0.00000001744702422",
"liquidity": { "usd": 20.7656 },
"volume": { "h24": 0 },
"priceChange": { "h24": 0 },
"source": "geckoterminal"
}
]
}The same call from JavaScript, in a page or a worker — no CORS setup needed:
const res = await fetch("https://dex.whyus.fun/api/market");
const { updatedAt, count, pairs } = await res.json();
// The ten biggest movers over the last hour.
const movers = pairs
.filter((p) => p.priceChange?.h1 != null)
.sort((a, b) => b.priceChange.h1 - a.priceChange.h1)
.slice(0, 10);
console.log(count, "pairs as of", updatedAt, movers.map((p) => p.baseToken.symbol));The catalogues and the queues:
curl -s https://dex.whyus.fun/api/boost/packages curl -s https://dex.whyus.fun/api/boost/queue curl -s https://dex.whyus.fun/api/advertise/packages curl -s https://dex.whyus.fun/api/advertise/queue
The handful of things this API is most often used for:
Gainers worth looking at
Rank by one window, but drop the pools too thin to trade — a 400% move on $200 of liquidity is noise:
const { pairs } = await fetch("https://dex.whyus.fun/api/market").then((r) => r.json());
const gainers = pairs
.filter((p) => (p.liquidity?.usd ?? 0) >= 25_000)
.filter((p) => (p.volume?.h24 ?? 0) >= 10_000)
.filter((p) => p.priceChange?.h24 != null)
.sort((a, b) => b.priceChange.h24 - a.priceChange.h24)
.slice(0, 20)
.map((p) => ({
chain: p.chainId,
token: p.baseToken.symbol,
price: p.priceUsd,
h24: p.priceChange.h24,
liquidity: p.liquidity.usd,
}));Just-launched pairs
pairCreatedAt is epoch milliseconds, so "new" is arithmetic rather than a provider flag:
const hourAgo = Date.now() - 60 * 60 * 1000;
const { pairs } = await fetch("https://dex.whyus.fun/api/market").then((r) => r.json());
const fresh = pairs
.filter((p) => (p.pairCreatedAt ?? 0) > hourAgo)
.sort((a, b) => b.pairCreatedAt - a.pairCreatedAt);Who is placed right now
The queues tell you which placements visitors are actually being shown: running is the count, and each row carries its own term. Buyer details are never in this response.
const queue = await fetch("https://dex.whyus.fun/api/boost/queue").then((r) => r.json());
console.log(queue.running, "of", queue.count, "boosts are live");
for (const order of queue.orders.filter((o) => o.running)) {
const hoursLeft = Math.round((order.expiresAt - Date.now()) / 3_600_000);
console.log(order.token.symbol, order.chainId, "--", hoursLeft, "h left");
}Poll without wasting requests
Every response carries updatedAt from one shared snapshot. Read it, and let it decide when to ask again — a client that respects it sees every update exactly once:
let seen = null;
async function tick() {
const snap = await fetch("https://dex.whyus.fun/api/market").then((r) => r.json());
if (snap.updatedAt !== seen) {
seen = snap.updatedAt;
console.log("new snapshot:", snap.updatedAt, snap.count, "pairs");
// ... your work here
}
}
setInterval(tick, 60_000);count simply drops. A pinned listing whose provider cannot answer is served from the app's last snapshot of it, so /api/pinned answers an empty pairs only when the operator has pinned nothing./api/market is one shared server-side snapshot refreshed at most every 30 seconds, so polling faster returns the same updatedAt — build your loop around that field rather than a fixed timer.There is no key, so there is no per-client quota to watch — but there is a shared obligation, because this deployment is what talks to the upstream providers. Its caches are what keep them inside their own free-tier limits:
| Endpoint | Refresh | What that means for you |
|---|---|---|
/api/market | at most every 30s | Polling faster returns the same updatedAt — key your loop off that field |
/api/pinned | at most every 30s | Cheap to read; it is the same kind of row, filtered to the pinned block |
| the two catalogues | at most every 30s | Prices change when the operator changes them — fetch once and trust it |
| the two queues | every request | Uncacheable by design, so it is the only cheap thing to poll when you are watching a placement |
5xx means an upstream provider was slow, not that you did anything wrong — wait, then retry. There is no documented limit to exceed, and no ban to earn, but a tight retry loop only lengthens the outage.A boost is a paid placement: it buys visibility in the strip above the screener, never a change to the data. The catalogue is public, and the queue is readable, but a placement itself is requested through the Boost page — it carries the buyer's contact handle, so it is not an anonymous write endpoint.
| Package | Price | Placement |
|---|---|---|
| 1 Hour | $9 | 1 hour |
| 6 Hours | $25 | 6 hours |
| 12 Hours | $39 | 12 hours |
| 24 Hours | $69 | 1 day |
| 7 Days | $179 | 7 days |
| 14 Days | $299 | 14 days |
| 30 Days | $499 | 30 days |
| 60 Days | $849 | 60 days |
| 90 Days | $1,149 | 90 days |
Placements can be bought for Solana, Ethereum, Base, BNB Chain, Arbitrum, Optimism, Polygon, Avalanche, Linea, Blast, Sonic, Cronos, Unichain, Ink, Scroll, ZKsync, Mantle, Celo, HyperEVM, Abstract, Monad, Berachain, Plasma, ApeChain, World Chain, opBNB, Katana, MegaETH, Stable, Robinhood Chain, Sui, Tron, TON, PulseChain, Fantom, Metis.
The fee is paid in crypto, from the buyer's own wallet, straight into one of two treasury wallets — there is no card, no custodian and no key held by the app:
| Rail | Treasury wallet |
|---|---|
| Solana (SOL) | EeGxCzMNh1YDNKuhDdApPDLEBQwYtfk3656AXbdJ1Ner |
| EVM networks (native coin) | 0xaaD8D32Cf2D3CDd1E4A4aF57896A9F1cAf65954F |
A boost bought on one of these networks is paid in that network's own coin, at the live DEX Screener rate, in one transfer:
0x1)0x2105)0x38)0xa4b1)0xa)0x89)0xa86a)0xe708)0x13e31)0x92)0x19)0x82)0x570)0x82750)0x1388)0xa4ec)A placement for any other chain is paid on Solana in SOL, so every package is payable without bridging. Once the transaction is in, the placement appears in /api/boost/queue with its payment.txSignature, the amount and the rail — check it on the chain's explorer before treating a placement as settled.
An advert is the same kind of paid placement for a non-token subject — a product, a website, a service or a launch — and it is bought and settled exactly like a boost: same packages-and-price catalogue shape, same two treasury wallets, same fee-in-crypto rail, same rule that a placement only opens once the chain confirms the fee. Adverts appear in a SPONSORED strip above the screener and on the Advertise page, and every card is labelled.
| Package | Price | Placement |
|---|---|---|
| 24 Hours | $49 | 1 day |
| 7 Days | $249 | 7 days |
| 30 Days | $799 | 30 days |
| 90 Days | $1,999 | 90 days |
/api/advertise/queue answers the same way /api/boost/queue does, with the advert's title, url, tagline, image and category in place of the token fields. The advertiser's contact handle and note stay on the server, for both.
The endpoints are unversioned and additive: a field is never renamed, never changes type and never disappears, so a client written today keeps working. New fields are added when a provider publishes something new — read what you need and ignore the rest, and treat every field the pair reference marks as optional as exactly that.
This page documents the shape as of 23 October 2026. If a change would ever have to break the shape above, it arrives at a new path instead, so the paths in the endpoint table are the fixed part of the contract.
The third-party sources' terms, limits and accuracy govern the numbers above; WHYUS? only merges and formats what they publish. GeckoTerminal and Uniswap are both called by this app's server, never by a visitor's browser — GeckoTerminal for trending pools, Uniswap's subgraph for a pair chart's candles — and DEX Screener is called from the browser by the screener itself. Either way the numbers are third-party: verify on-chain before you act on them.
This document describes how the WHYUS? app currently behaves. WHYUS? is owned and operated by whyus.fun, reachable on X (@whyusdotfun) and Telegram (@whyusdotfun). Operational details only the operator can supply — the legal entity, its registered address and the governing jurisdiction — are not published in the app yet and must be added before this document is relied on commercially.