API v1
PairsFun public API
Every PairsFun token, its pools, its fees, $PAIRS supply and burns, and the protocol totals, as JSON over HTTPS. No key, no sign-up, and CORS is open to any site, so a page can call it straight from the browser.
Quick start
Base URL https://pairsfun.xyz/v1 (the same calls also answer under /api/v1). Plain GET requests, nothing to sign up for.
curl "https://pairsfun.xyz/v1/tokens?sort=mcap&limit=10"
curl "https://pairsfun.xyz/v1/tokens/WecNBsywJKQLGfBKZzBSix3ykXJ11DfAGUiXCLypair"
curl "https://pairsfun.xyz/v1/stats"
From a browser or a bot, with fetch only. This reads every token on the bonding curve, page by page:
const API = 'https://pairsfun.xyz/v1'
async function get(path) {
const res = await fetch(API + path)
const json = await res.json()
// Errors always look the same: { error: { code, message, status, requestId, retryAfterSec? } }
if (!res.ok) throw Object.assign(new Error(json.error.message), json.error)
return json
}
const tokens = []
let cursor = null
do {
const page = await get('/tokens?status=curve&sort=progress&limit=100' + (cursor ? '&cursor=' + cursor : ''))
tokens.push(...page.tokens)
cursor = page.nextCursor
} while (cursor)
for (const t of tokens) console.log(t.symbol, t.curve.progressPct + '%', t.marketCapUsd)
Endpoints
All of them are GET and need no key. The cache column says how long the same answer is served to everyone; polling faster than that returns the same data.
| Call | What it returns | Cache |
|---|---|---|
| GET /status | Is the API up, is launching open, the bonding-curve launch model (start market cap, raise target, supply split), the currencies a curve can raise in, the official $PAIRS mint. | 10 s |
| GET /tokens | Every listed token, with filters, search, sorting and pages. See Token list. | 10 s |
| GET /tokens/{mint} | One token in full: the token object plus description, links, supply, launch and migration transactions, every pool, and the on-chain checks shown on its page. | 10 s |
| GET /tokens/{mint}/chart | Price history of a bonding-curve token, up to 720 points of [unix seconds, price] in its curve currency. | 30 s |
| GET /tokens/{mint}/pools | Multipair pools of a graduated token: one pool per asset its creator picked, opened from the 10% of supply set aside for them. | 30 s |
| GET /tokens/{mint}/creator-fees | The creator fee of a graduated token's main pool: collected so far, the creator's part, paid and waiting, the treasury part, with transactions. | 15 s |
| GET /stats | Protocol totals: launches, raised, volume, fees, liquidity, daily volume, top tokens by volume and market cap, the latest launches and graduations. | 30 s |
| GET /fees | Fee rates on the curve and in the main pool, and how the PairsFun fee from bonding curves is split when the split is on (multipair pools, treasury, $PAIRS bought back and burned by the pools key), with its live totals and whether it is on now. | 30 s |
| GET /buyback | $PAIRS supply, how much was burned and bought back so far, the treasury, and the transactions. | 30 s |
| GET /prices?mints= | USD prices for 1 to 50 tokens, comma-separated. A token with no price right now is left out. | 15 s |
| GET /pairs, /pairs/{mint} | Assets a token can be paired with, and one asset in detail: price, decimals, issuer powers. | 60 s |
| GET /launches | Instant multipairs only, the earlier launch model, in their original shape. For every token use /tokens. | none |
| GET /config, /openapi.json | Launch settings for partner sites, and the full contract of this API. | none |
Token list
GET /tokens lists every token on PairsFun. Test coins and tokens taken down by moderation are not in it.
| Parameter | Values |
|---|---|
| status | all (default), curve (on the bonding curve, including a token migrating right now), graduated (migrated into its main pool), multipair (instant multipairs, the earlier model). |
| sort | new (default), mcap, volume (24 hours), progress (bonding-curve progress, graduated count as 100). Tokens without the value go last. |
| q | Part of the name or ticker, a full mint address, or the ticker of a pair asset (TSLAx). Up to 64 characters. |
| limit | 1 to 100, default 50. |
| cursor | nextCursor from the previous page. It is null on the last page. |
The answer is { "tokens": [...], "nextCursor": "50", "total": 214 }, where total counts every token that matches status and q. A new launch can shift a page by a token or two while you page through, so de-duplicate by mint.
The token object
The same object comes in /tokens, in the top lists of /stats, and inside /tokens/{mint} (with more fields). The official $PAIRS, as an example:
{
"mint": "WecNBsywJKQLGfBKZzBSix3ykXJ11DfAGUiXCLypair",
"name": "PairsFun",
"symbol": "PAIRS",
"image": "https://pairsfun.xyz/meta/img/WecNBsywJKQLGfBKZzBSix3ykXJ11DfAGUiXCLypair.png",
"status": "graduated",
"createdAt": "2026-11-05T04:28:05.000Z",
"creator": "C478RVVWiAg2eFBr8BTw619qhkKSQFQuudEZUuqNtw5y",
"decimals": 6,
"priceUsd": 0.0000496,
"marketCapUsd": 49601.22,
"liquidityUsd": 15368.72,
"volume24hUsd": 338568.45,
"volumeAllUsd": 338568.45,
"curve": {
"pool": "F3vscNPnybdZJG7k3tWG7XdHCGZZBAs8nYoxRd3vt1cT",
"quoteMint": "So11111111111111111111111111111111111111112",
"quoteDecimals": 9,
"progressPct": 100,
"raisedRaw": "200000000196",
"targetRaw": "200000000000",
"priceInQuote": 5.134e-7,
"migrating": false
},
"mainPool": "4qq6m4MMfd84fW5USDMWvzvFoJZVETsuuVuqvLRuuBW3",
"pairAssets": [
{ "mint": "XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB", "symbol": "TSLAx", "weightBps": null, "pool": null },
{ "mint": "Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh", "symbol": "NVDAx", "weightBps": null, "pool": null },
{ "mint": "XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W", "symbol": "SPYx", "weightBps": null, "pool": null }
],
"fake": false,
"url": "https://pairsfun.xyz/coin/WecNBsywJKQLGfBKZzBSix3ykXJ11DfAGUiXCLypair"
}
| Field | Meaning |
|---|---|
| status | curve, graduated or multipair. |
| priceUsd, marketCapUsd | USD per token and for the whole supply. null when the price of its pool currency is not known right now, never a made-up 0. |
| liquidityUsd | Real liquidity in its pools, in USD. |
| volume24hUsd, volumeAllUsd | Estimates from pool counters. null when not counted yet. |
| curve | Bonding-curve state: progress in percent, raised and target in base units of the curve currency, the price in it. null for instant multipairs. |
| mainPool | The pool a graduated token migrated into. null until then. |
| pairAssets | For a curve token, the assets its creator picked for multipair pools after migration (the pools are in /tokens/{mint}/pools). For an instant multipair, its basket, with weights and pools. |
| fake | true when the name or ticker copies PairsFun or $PAIRS (with fakeReason), false when checked and clean, null when not checked yet. |
GET /tokens/{mint} answers { "token": { ... } } with all of the above plus description, links (the creator's website, X, Telegram), supplyRaw, launch (dev buy, launch and migration transactions), pools (each with its role: curve, main, basket or multipair) and proofs, the on-chain checks shown on the token page, each with a link to check it yourself.
Conventions
- Amounts whose name ends in
RaworLamportsare integers in base units, as strings, so nothing is lost to floating point. Divide by10^decimals(SOL has 9). - USD values are numbers.
nullmeans not known right now. - Times are ISO 8601 in UTC, such as
2026-11-05T04:28:05.000Z. Chart points use unix seconds to stay compact. - Pct is a percent (
0.5means half a percent). Bps are basis points (4500means 45%). - Unknown fields: ignore them. New fields and new enum values can appear at any time within v1.
Limits
60 requests a minute per IP address (IPv6 by /64), for all the calls on this page together. A token page also counts against the per-visitor limit of pairsfun.xyz itself.
A key from the partner program reads with its own limit, 600 a minute per key, sent as Authorization: Bearer <key>. A publishable key works only from its registered origins, as for launches.
Every answer, a 304 and an error included, carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds); only /config and /openapi.json have no limit and no such headers. Over the limit you get 429 rate_limited with Retry-After, the same number in error.retryAfterSec. Need more? DM @pairsfun_xyz on X.
Caching
Answers are shared: everyone asking the same thing within the cache time in Endpoints gets the same answer, and it comes with Cache-Control: public, max-age for the time it has left. Each answer also carries an ETag: send it back as If-None-Match and an unchanged answer is 304 Not Modified with no body. A 304 still counts toward the limit.
Errors
HTTP 404
{
"error": {
"code": "not_found",
"message": "No PairsFun token with this mint.",
"status": 404,
"requestId": "req_9f2c41d07a3b55e1"
}
}
Branch on code, show message, quote requestId when you write to us. The text can change, the code does not.
| Code | What happened |
|---|---|
| validation_failed | A parameter is wrong; details.field names it. HTTP 400. |
| not_found | No such token, or it has no such data yet (no multipair pools, no creator fee payouts). HTTP 404. |
| method_not_allowed | Not a GET. The Allow header says what is. HTTP 405. |
| rate_limited | Over the limit. Wait retryAfterSec. HTTP 429. |
| warming_up | The server has just restarted and is still reading the pools. Retry after retryAfterSec, a few seconds. HTTP 503. |
| upstream_unavailable | The network node or a price source did not answer. Try again in a moment. HTTP 502. |
| unauthorized, forbidden_origin | Only when you send a key: an unknown or revoked key, or a publishable key from an origin that is not registered. |
| internal_error | Something broke on our side. HTTP 500. |
Versioning
This is v1. Within v1 fields and endpoints are only added, never renamed or removed, and an enum can gain new values. A breaking change would come as /v2, with v1 kept alongside it. The OpenAPI spec is the contract; its info.version goes up with every addition.
Launching tokens from your own site instead of reading them? That is the partner API, on the same base URL.