API Reference
Public read endpoints: pool discovery, swap routing, statistics, charts, and XP
Base URL: https://api.coffer.so. The endpoints on this page are public:
no API key, no sign-in. They read indexed data; trades and liquidity
operations are signed Solana transactions (see the SDK).
Interactive OpenAPI documentation with exact schemas lives at
https://api.coffer.so/docs. Endpoints that
back the Coffer app itself (wallet sign-in, portfolio, referrals, pool
registration and renaming) are not covered here: create and manage pools
through app.coffer.so.
Conventions
- Addresses are base58; token amounts and LP supply are raw integer strings. Calculate with
bigint. swapFee,protocolFeeRateandweightare display percentages:swapFee: 0.3means 0.3%;protocolFeeRate: 20means the protocol takes 20% of the base fee.- Pagination:
limit1–100 (default 50),offset0–10,000 (default 0). - Standard envelope:
{ "success": true, "data": ... }; paginated responses addhasMoreandtotalCount. The exceptions below return a flat object. - Rate limits are per IP: 10 requests per second and 200 per minute across the API; chart endpoints have their own per-IP limits. On
429, back off. - Indexed state lags the chain by seconds to minutes. Refresh from chain before building a transaction.
Pool and token discovery
| GET | Parameters / result |
|---|---|
/api/pools | Pagination. Pinned pools first, then newest |
/api/pools/:address | One pool with its tokens; 404 if unknown |
/api/pools/by-pair?tokenA=<mint>&tokenB=<mint> | Pools holding both mints, TVL descending |
/api/pools/tokens | Every token that appears in a pool, with the pools it belongs to |
/api/pools/top-tokens?limit=30 | limit 1–100; tokens ordered by priority, market cap, ticker |
Discovery can include disabled pools: check poolEnabled and swapsEnabled.
Pool fields include poolAddress, poolName, baseAssetMint, swapFee,
protocolFeeRate, poolEnabled, swapsEnabled, poolAdmin, bptMint,
bptTotalSupply, bptPriceUsd, tvlUsd, virtualTvlUsd, volume24h,
apy, apy7d, apyUnreliable, the range-manager settings
(rangeManager*) and tokens. Each token carries mintAddress, ticker,
decimals, weight, leverage (rounded for display), actualBalance,
virtualBalance, vaultAddress, tokenProgram, orderIndex, imageUrl,
isActive and the sell-off window settings (maxSelloffPct,
variableFee*, currentSelloff, windowStartTimestamp).
apyis the 24-hour figure shown in the app,apy7dthe 7-day one; both are compounded, see How Yield Is Generated.apyUnreliable: truemeans the pool's TVL is too small for the figure to mean anything and both are reported as0.actualBalanceis already net of protocol fees: never subtractprotocolFeesOwedagain.protocolFeesOwedis not returned; read it on chain.- Metrics and token metadata can be
nullwhile a pool is being indexed.
Swap routes
GET /api/pools/swap-route?tokenIn=<mint>&tokenOut=<mint>&amountIn=<raw>&decimalsIn=9| Parameter | Meaning |
|---|---|
tokenIn, tokenOut | Distinct mints |
amountIn | Raw input, positive integer string (at most 20 digits; execution must fit u64) |
decimalsIn | 0–18, default 9. Use the mint's real decimals so USD-based fields are right |
pool | Optional: quote through this one pool only, no split |
slippageBps | 0–10,000, default 200 (2%). Used to derive minReceived and per-leg minAmountOut |
Response, with placeholder addresses:
{
"success": true,
"data": {
"routes": [{
"poolAddress": "<pool>",
"poolName": "SOLUSDC",
"amountIn": "1000000000",
"expectedOut": "149000000",
"minAmountOut": "146020000",
"percentage": 100,
"swapFee": 0.3,
"surgeFeeAmount": "0",
"surgeFeePct": 0,
"tokenInIndex": 0,
"tokenOutIndex": 1,
"tokenProgramIn": "<input-token-program>",
"tokenProgramOut": "<output-token-program>",
"vaultIn": "<input-vault>",
"vaultOut": "<output-vault>"
}],
"totalAmountIn": "1000000000",
"totalExpectedOut": "149000000",
"minReceived": "146020000",
"slippageBps": 200,
"effectivePrice": 0.149,
"spotPrice": 0.15,
"priceImpact": 0.36777,
"feePercent": 0.3,
"totalSurgeFee": "0",
"estimatedXp": 0,
"xpBoostApplied": false
}
}| Field | Semantics |
|---|---|
routes | Direct-pair legs across Coffer pools; raw amounts per leg |
percentage | Share of the input sent through this leg, two decimals |
expectedOut, minAmountOut | Quoted output per leg (net of surge) and the floor derived from slippageBps |
surgeFeeAmount, surgeFeePct | Output-side dynamic fee of this leg, if the pool's sell-off window is above its threshold |
totalAmountIn, totalExpectedOut, minReceived | Totals across legs |
effectivePrice | Raw output / raw input |
spotPrice | Best candidate pre-fee raw spot price |
priceImpact | Percent deviation from the best fee-adjusted candidate spot, clamped at zero |
feePercent | Input-weighted base fee, display percent |
estimatedXp | Expected XP for this trade; 0 can also mean a missing USD price |
xpBoostApplied | true when a signed-in app user has a referral boost; always false for anonymous calls |
The route is a quote, not a transaction. HTTP 200 with an empty routes
array or zero output means no usable route. Swap Routing
covers how to turn a route into a protected transaction.
Transactions and aggregate statistics
| GET | Result / filters |
|---|---|
/api/pools/:address/transactions | Pagination; optional user=<wallet> and type=swap|add_liquidity|remove_liquidity|zap; newest first |
/api/pools/:address/tx-stats | { totalCount, count24h } |
/api/pools/stats | Protocol totals: totalTvlUsd, totalVirtualTvl, totalVolume24h, poolCount, updatedAt |
Transaction rows carry raw amounts (amountIn, amountOut, feeAmount,
protocolFeeAmount, surgeFeeAmount), their USD values, the pool state
after the transaction (virtualBalances, actualBalances,
normalizedWeights, bptTotalSupply) and blockTime/slot. Base fees
and their protocol share are in input-token units; surge fees in
output-token units. zap is a single-token deposit.
Time series
GET /api/stats/:metric?pool=<address>&window=7dMetrics: tvl, volume, swap_count, avg_swap, median_swap,
fees_lp, fees_protocol, users_total, dau, mau, deposits,
removals. pool defaults to all (the whole protocol). Windows: 1d,
7d (default), 30d, all (everything since launch). Points are
ten-minute buckets.
Flat response: { "points": [{ "t": 1714003200000, "v": 1234567.89 }] },
t in Unix milliseconds, v in USD or a count.
Charts
The series behind the app's charts, on a uniform time grid and cached for a couple of minutes.
| GET | Result |
|---|---|
/api/pools/:address/chart?metric=apy&range=1m&basis=7d | One pool metric over time. metric: apy (default), tvl, volume, fees. range: 24h, 1w, 1m, 6m, 1y, all. For apy, basis is the rolling window behind each point: 7d (default, smooth) or 24h (matches the pool card headline). Returns granularitySec, current, change and points: [{ t, v }] |
/api/pools/:address/token-chart?range=1w&token=<mint> | Price of every pool token quoted in the pool's base token, with the live rate and the curve's price bounds (the range slider). token narrows to one mint. range: 1d, 1w, 1m, 1y, all |
/api/tokens/pair-chart?a=<mint>&b=<mint>&range=1w | Price of a in b over time for any two priced mints, pool or not: current { ratio, aUsd, bUsd }, change { abs, pct }, series. Same ranges; all is capped at one year. 90 requests per minute per IP |
Pair-chart ratios keep six significant digits (BONK/SOL-style prices are tiny); a mint without a known USD price history yields a flat line at its current price.
XP leaderboard
Read-only views of Coffer XP. Flat responses.
| GET | Result |
|---|---|
/api/leaderboard?page=1&limit=20 | { total, page, limit, data: [{ address, points, place }] }; limit 1–100 |
/api/leaderboard/stats | Aggregate users and XP |
/api/leaderboard/epoch | Current epoch, multiplier, base and current rates, epoch history |
/api/leaderboard/user/:address | A wallet's points and rank |
/api/leaderboard/user/:address/history?page=1&limit=20 | The wallet's accrual history |
swapXpPerUsdLpFee applies to the trade's entire base fee in USD (LP
plus protocol share, excluding surge). currentRates.lpXpPerUsd is per
three-hour run; lpXpPerUsdPerDay is the daily figure shown in the app.
Errors
Errors use { "success": false, "error": "..." } with the HTTP status:
400 invalid input, 404 unknown pool or wallet, 429 rate limited,
500 server error.
Health and version
GET /health → { "status": "ok", "timestamp": "..." }
GET /api/version → { "version": "...", "timestamp": "..." }Health is HTTP liveness only, not indexing freshness.