Feast public read API — v1

Chain id 56 · schema version 1 · index verdict serving — block 128511955, 4 behind the head, 0 open gap(s).

Four things and no fifth: discovery (what launched), quote (what a trade returns), events (what happened), candles (what to draw). No write endpoints, no keys, no accounts, and no trade construction on the server.

Conventions

Base/api/v1
VersioningIn the path. v1 never changes shape; fields are added, never repurposed. A breaking change is v2.
ChainEvery response carries chainId. A client that does not check it is a client that will trade the wrong chain.
AmountsDecimal strings of base units, never JSON numbers — a uint256 does not fit in a double.
TimesRFC3339 UTC and blockNumber: a bot reconciles on blocks, a human reads dates.
Errors{"error":{"code","message"}}. Codes are stable strings; messages are not.
Rate limit60 requests a minute per IP, across the whole of v1. X-RateLimit-* headers; 429 carries retry-after.
CORS* on every GET. These are public reads.
sourceindexer or chain — see below.

What source means, exactly

A pool's structure — the full poolKey, the opening window, the fee split, the coin's name — is always a chain read; the index holds events, and a pool key is state. source names where the market figures came from: 24-hour volume, trade count, holders and last price. When it says chain, the market object on each pool is null rather than zero — an absent figure and a zero figure are different facts.

GET /api/v1/pools

The new-launch feed. Cursor-paged, never offset-paged: a launch arriving between two pages would shift every later row by one, and an offset-paged consumer would silently skip a coin.

GET /api/v1/pools?order=created_desc&limit=50
GET /api/v1/pools?created_after=2026-09-18T00:00:00Z
GET /api/v1/pools?quote=0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
GET /api/v1/pools/{poolId}
ordercreated_desc (default) or created_asc
limit1–200, default 50
cursorOpaque, from the previous nextCursor. A cursor this endpoint did not issue is a 400; a cursor whose row has since gone is an empty page.
created_afterRFC3339. Requires the index; coins with no indexed launch time are excluded rather than assumed recent.
quoteQuote asset address

Cached 12 s at the edge. /api/v1/pools/{poolId} adds live state and the hook's fee buckets, with accrued and paid kept apart — they are different facts and summing them answers nothing.

The opening window. The hook refuses a buy over opening.perWalletCap (PurchaseCapExceeded) in the pool's first two blocks with trades, counted from its first public trade, however long that takes: waiting does not lift it, trading does. opening.servedBlocks is the hook's own count and opening.capBinding is true until it reaches 2. It refuses entirely before opening.openAtSecond (PoolNotOpenYet; zero on every ordinary coin). A creator's own pre-buy at launch is exempt and does not start the window. A sniper sizing off pool liquidity alone gets a reverted transaction and files the coin under "rug".
Changed with the v1.6.21 contracts: opening.capEndBlock and opening.openAtBlock are now always null. The window no longer ends at a block known in advance, so there is no number to publish.

GET /api/v1/quote

A simulation, not a formula, against 0xd0737C9762912dD34c3271197E362Aa736Df0926 — PancakeSwap's own CLQuoter, named in quotedVia so you can re-run the identical call yourself.

GET /api/v1/quote?poolId=0x…&side=buy&amountIn=10000000000000000
GET /api/v1/quote?coin=0x…&side=sell&amountIn=1000000000000000000&referrer=0x…
What "1%" measures, exactly. The hook takes 1% of the amount that reaches the pool — amountIn / 101 — which is 0.990% of the gross input, and the same again on the way out of a sell: a round trip costs 2.01% of the stake. This is charged on top of poolKey.fee (0.01%), which is why hookFeeBps: 100 is published on every pool. A router that reads only poolKey.fee and does closed-form maths quotes 1% high, and its slippage guard reverts the trade. Use amountOut from this endpoint; it is the simulated number and it already has the fee taken out. hookFee.amount is derived: true — the documented rule applied to your input, not a second simulation.
Where the fee goes when nobody refers. An outside router passes empty hookData, so no referrer is credited and the referrer's share falls to the creator — the feeSplit returned reflects that fold. Feast's platform share is unaffected by who routed the trade. Pass ?referrer=0x… to price the trade you would actually send.
How to name a referrer (v1.6.26). Put one 32-byte word in hookData: the referrer's address, abi.encode(referrer). Send empty bytes when nobody referred the trade. The 64-byte abi.encode(referrer, wallet) form still parses but binds nobody; there is no reason to send it. A referrer named this way is paid for that trade only. A wallet that has bound a referrer in the FeastReferrals registry — by its own call, or by its own EIP-712 signature relayed for it — pays that referrer on every trade it originates, and the hook does not read hookData for it; feeSplit here is the split for an unbound wallet.

A reverted simulation is a 200 with a named refusal, not an error: it is a true answer to "what would this trade do". PurchaseCapExceeded carries retryAfterBlock: once the window's two blocks have traded, a buy in any block after it is uncapped. While fewer than two have traded, the cap lifts after the next block somebody trades in successfully, which nobody can name yet, so retryAfterBlock is null and opening.capBinding is true. A buy under perWalletCap goes through and counts its block like any other.

Exact-output quotes (amountOut=) are not in v1 and are refused by name rather than silently treated as exact-in.

GET /api/v1/events

One ordered stream — launches, trades and fee rows — filterable and replayable. Every event carries txHash + logIndex, which is its identity; cursor is opaque and monotonic, and since_cursor is exclusive.

GET /api/v1/events?types=launch,trade,fee&limit=200
GET /api/v1/events?poolId=0x…&types=trade&since_cursor=…
GET /api/v1/events?types=window&limit=200

Ordered by (blockNumber, logIndex), ascending. Every event carries depth — final, confirmed or tip — because a tip row may still unwind, and it is served labelled rather than withheld or served silently. Fee events carry settled: accrued and paid are different facts.

types=window is the FEAST sale's launch window, served only when named: no types means launch,trade,fee. A window event's kind is the contract's event — Committed, Withdrawn, Settled, Claimed or Refunded — beside account, amount and amountAsset: coin on a claim, which pays FEAST, and quote on the other four. total is the pot a commit, a withdrawal or the settlement states. The settlement also carries sold, the FEAST tranche sold, and clearingSqrtPriceX96. A window event names no coin and no pool, so a coin= or poolId= page has none.

Requires the index; there is no chain fallback, because the chain reads would give a sequence with holes in it under a cursor that claims completeness — and missing events look exactly like a quiet market.

GET /api/v1/payouts

The newest fee payouts, newest first — the tail the stream deliberately has no cursor for. A payout has settled: FeeAccrued and ReferralCredited are money still in the pool and are on /events, not here.

GET /api/v1/payouts?limit=6
GET /api/v1/payouts?coin=0x…&limit=20

Ordered by (blockNumber, logIndex), descending, and bounded at 50. No cursor: there is nothing after the newest row to resume from, and a consumer that wants to walk the whole ledger has /api/v1/events, which is ascending and resumable. Each row carries seat — creator, referrer, house or burn — and amountAsset, which says whether amount is in the pool's quote (five of the six kinds) or in the coin itself (FilledCoinBurned). Read it: the two are different tokens.

Requires the index; there is no chain fallback, because no chain read answers "the last six payouts across every pool" without the walk the index exists to prevent.

GET /api/v1/ohlcv

Candles, in GeckoTerminal's own row shape: [ts, o, h, l, c, v], ts in unix seconds. Anyone who has written a GeckoTerminal client has written a Feast client.

GET /api/v1/ohlcv?poolId=0x…&timeframe=minute&aggregate=5&limit=300

timeframe/aggregate: minute 1/5/15, hour 1/4, day 1. An unsupported pair is refused rather than rounded to the nearest one — a chart drawn on a silently different bucket disagrees with its own axis.

GET /api/v1/announcements/{week}

The weekly settlement artifact, once FeastEmissions.settle(n) has run for that week: fees per quote asset, FEAST emitted and burned, the three tracks' pots and shares, and who claimed. Every figure carries its source — the contract, the block, and the event or function it came from — because this is a claim about money made to people who were not in the room.

GET /api/v1/announcements/2958

A week that has not settled is a 404, never a document full of zeroes: before settlement every pot is zero, and publishing that would read as a quiet week rather than as a job that has not run. A settled week is immutable, so the artifact is built into the deployment and cached for an hour.

Not in v1, on purpose