Reference
The whole surface
Every endpoint, every parameter, every cursor rule. /v1/schema is the machine-readable version of this page, and it is free.
Overview
fomo402 indexes the public FOMO feed (callouts with a written thesis, and the on-chain buys and sells behind them) and serves it as JSON over HTTP. Every response is priced in USDC and gated by 402 Payment Required per the x402 protocol. There is no account, no API key and no invoice.
Base URL is https://fomo402.xyz. Every endpoint is GET, answers application/json, and allows any origin. Times are epoch milliseconds unless stated otherwise.
- Records are the raw rows: one call or one trade, with its actor, token, trade and conviction blocks attached.
- Rollups aggregate the same rows by handle or by mint: call counts, volume, distinct callers, realized PnL.
- Leaderboards rank those rollups over a time window you choose.
/v1/stats (free) for the live window before you build on a date range.Paying
The handshake is plain x402, version 1. Request without an X-PAYMENT header and the server quotes you:
HTTP/1.1 402 Payment Required
content-type: application/json
{
"x402Version": 1,
"error": "X-PAYMENT header is required",
"accepts": [
{
"scheme": "exact",
"network": "solana",
"maxAmountRequired": "2000",
"resource": "https://fomo402.xyz/v1/calls?minFollowers=5000",
"description": "fomo402 /v1/calls — Callouts with a written thesis",
"mimeType": "application/json",
"payTo": "11111111111111111111111111111111",
"maxTimeoutSeconds": 60,
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"extra": { "feePayer": "<facilitator fee payer>", "decimals": 6 }
}
]
}asset is the SPL mint, payTo is a wallet address rather than its associated token account, and extra.feePayer is the facilitator account that pays the network fee, so a payer needs no SOL of their own.
Build a Solana transaction that transfers exactly maxAmountRequired atomic units of that mint to payTo, set feePayer as the transaction fee payer, sign it as the token owner, and retry the identical request with { "transaction": "<base64>" } as the payload in X-PAYMENT. The facilitator adds its signature at settlement. Any x402 client library does this for you.
Order of operations
- Verify. The payload goes to the facilitator at
https://facilitator.payai.network. An invalid payment is a402with the reason inerror, and nothing is charged. - Serve. The server builds the body.
- Settle. The payment settles on chain. A settlement failure downgrades the whole response to
402rather than releasing the body. - Respond.
200with the JSON, plusX-Payment-Response: base64 of the settlement result, transaction signature included.
A 200 from this API means the payment cleared; a 402 means it did not.
mock mode the server accepts any well-formed payload and settles nothing. Use it against a local build, never against a real wallet. Responses name the mode that answered them in x-payment-mode.Free tier
Every IP gets 25 unpaid requests per UTC day across the whole surface. They answer exactly like a paid call and carry:
x-payment-mode: free-tier
x-free-tier-remaining: 21
x-price-usd: 0.002000Once the allowance is gone the same request returns the 402 challenge. /v1/stats, /v1/schema and /.well-known/x402 never consume it: they are free.
Pricing
Per response. A 500-row page costs the same as a 1-row page, so a wide limit is the cheapest way to walk anything.
| Endpoint | Price | Atomic | What you get |
|---|---|---|---|
| /v1/feed | $0.002 | 2000 | Calls and trades in one stream |
| /v1/calls | $0.002 | 2000 | Callouts with a written thesis |
| /v1/trades | $0.002 | 2000 | On-chain buys and sells |
| /v1/search | $0.005 | 5000 | Full-text over thesis, ticker, mint, handle |
| /v1/records/{id} | $0.001 | 1000 | One record by id |
| /v1/actors/{handle} | $0.003 | 3000 | Actor profile with rollup stats |
| /v1/actors/{handle}/calls | $0.002 | 2000 | Everything one actor has posted |
| /v1/actors | $0.005 | 5000 | Actor directory, ranked |
| /v1/tokens/{mint} | $0.003 | 3000 | Token rollup: buyers, volume, callers |
| /v1/tokens/{mint}/calls | $0.002 | 2000 | Everything said about one mint |
| /v1/tokens | $0.005 | 5000 | Token directory, ranked |
| /v1/leaderboard | $0.01 | 10000 | Actors ranked by conviction, volume or PnL |
| /v1/stats | free | 0 | Index size, poll health, coverage window |
| /v1/schema | free | 0 | OpenAPI 3.1 document |
| /.well-known/x402 | free | 0 | x402 resource discovery |
Amounts are atomic units of USDC (6 decimals) on solana.
Pagination
Every list response carries a pagination block. Follow nextCursor until hasMore is false and you have every row that matched.
"pagination": {
"limit": 200, // rows requested
"count": 200, // rows on this page
"total": 12841, // rows matching the filter, all pages
"hasMore": true,
"nextCursor": "azoxNzI2NTQzMjEwMDA6ZjhhMg",
"seq": 481203 // index sequence when the page was cut
}The same values are mirrored into headers, so a client that streams responses does not have to parse the body to keep walking: X-Next-Cursor, X-Total-Count, and an RFC 8288 Link: …; rel="next" with the full next URL.
Two cursor modes
- Keyset: the default, whenever
sort=ts. The cursor names an exact position in the(ts, id)ordering. Rows arriving at the head while you walk cannot cause a skip or a repeat. Use this for exhaustive reads. - Offset: used by value sorts (
usd,mcap,followers,pnl,likes,holdings,conviction) and by the actor, token and leaderboard directories, which have no stable key. A long walk can shift if the ranking changes underneath it. Use these for the top of a list, not the whole of one.
Cursors are opaque: base64url, with the encoding outside the contract. Never build one by hand, and never carry one across a change of sort. The server answers 400 there by design, rather than handing you a wrong page.
limit defaults to 50 and caps at 500.
Filters
Every parameter below applies to all record endpoints. The index evaluates them before it cuts the page, so total reflects your filter rather than the raw corpus. Repeat a parameter or comma-separate it: handle=a&handle=b and handle=a,b are the same query.
| Parameter | Type | Meaning |
|---|---|---|
| kind | string | call or trade. Pinned on /v1/calls and /v1/trades. e.g. call |
| view | csv | Upstream views the row appeared in: latest, trending, conviction. e.g. trending,conviction |
| side | string | buy or sell. e.g. buy |
| handle | csv | Only these handles. Leading @ optional, case-insensitive. e.g. cooker,ansem |
| excludeHandle | csv | Drop these handles. e.g. spamdev |
| mint | csv | Only these contract addresses. e.g. EZdUAto…pump |
| symbol | csv | Only these tickers. Leading $ optional. e.g. $wif,bonk |
| chain | string | Network id as upstream reports it. e.g. solana |
| q | string | Search terms. Every term must appear in thesis, ticker, mint, handle or name. e.g. runner low float |
| minFollowers | integer | Author follower floor. e.g. 5000 |
| maxFollowers | integer | Author follower ceiling, for finding early accounts. e.g. 50000 |
| minUsd | number | Minimum trade or callout size in USD. e.g. 2500 |
| maxUsd | number | Maximum trade size in USD. |
| minMcap | number | Market cap floor at the time of the row. e.g. 250000 |
| maxMcap | number | Market cap ceiling. e.g. 5000000 |
| minHoldingsUsd | number | Minimum position still held. |
| minConvictionUsd | number | Minimum total the author has bought of this token. |
| minBuyers | integer | Token traction floor: distinct buyers. |
| minPnlPct | number | Minimum realized PnL percent on the row. |
| minLikes | integer | Minimum likes on the callout. |
| hasText | boolean | Rows with (or without) a written thesis. |
| hasAccount | boolean | Rows from a FOMO account, versus raw wallets. e.g. true |
| hasWallet | boolean | Rows carrying a wallet address. |
| closed | boolean | Calls the author has since closed out. |
| hideDevs | boolean | Drop rows whose author is flagged as the token dev. |
| since | time | Lower time bound. Epoch ms, epoch seconds, ISO date, or an offset like -6h. e.g. -6h |
| until | time | Upper time bound, same formats as since. |
Shape and paging
| Parameter | Type | Meaning |
|---|---|---|
| sort | string | ts (default), usd, mcap, followers, pnl, likes, holdings, conviction. |
| order | string | desc (default) or asc. |
| limit | integer | Rows per page, 1–500. Defaults to 50. |
| cursor | string | Opaque cursor from pagination.nextCursor. Never build one by hand. |
minUsd=1000. The filter reads a missing value as zero rather than unknown, so a floor always narrows.since and until accept four forms: epoch milliseconds, epoch seconds, an ISO 8601 date, or a relative offset: -30m, -6h, -7d. The server resolves the relative form when the request lands.
Record endpoints
All four list endpoints take the full filter set and paginate identically. They differ only in what they pin.
| Endpoint | Price | Notes |
|---|---|---|
| /v1/feed | $0.002 | Calls and trades in one stream |
| /v1/calls | $0.002 | Callouts with a written thesis |
| /v1/trades | $0.002 | On-chain buys and sells |
| /v1/search | $0.005 | Full-text over thesis, ticker, mint, handle |
| /v1/records/{id} | $0.001 | One record by id |
/v1/calls pins kind=call, /v1/trades pins kind=trade, and /v1/feed pins nothing. /v1/search requires q and is otherwise identical to /v1/feed; every whitespace-separated term must appear somewhere in the thesis, ticker, mint, handle or display name, so terms narrow rather than widen.
GET https://fomo402.xyz/v1/calls
?minFollowers=5000 # author has real reach
&minUsd=2500 # and put real size behind it
&maxMcap=3000000 # still early
&hasText=true # wrote a thesis, not just a fill
&hideDevs=true
&since=-24h
&sort=ts&limit=500Actors & tokens
Rollups over the same index, keyed by handle or by mint. They aggregate what this API has indexed, so their counts describe its window rather than an account's whole history upstream.
| Endpoint | Price | Notes |
|---|---|---|
| /v1/actors | $0.005 | Actor directory, ranked |
| /v1/actors/{handle} | $0.003 | Actor profile with rollup stats |
| /v1/actors/{handle}/calls | $0.002 | Everything one actor has posted |
| /v1/tokens | $0.005 | Token directory, ranked |
| /v1/tokens/{mint} | $0.003 | Token rollup: buyers, volume, callers |
| /v1/tokens/{mint}/calls | $0.002 | Everything said about one mint |
| /v1/leaderboard | $0.01 | Actors ranked by conviction, volume or PnL |
- /v1/actors and /v1/tokens take
sort,q,limit,cursor, plusminFollowersandminCallersrespectively. Actor sorts:calls,volume,conviction,pnl,followers,likes,tokens. Token sorts:calls,volume,callers,mcap,buyers. - /v1/actors/{handle}/calls and /v1/tokens/{mint}/calls take the full record filter set and paginate by keyset, served off a per-handle and per-mint index, so walking a prolific account does not rescan the corpus per page.
- /v1/leaderboard takes
metric(defaultconviction) andsince(default-24h), and returns each row with arankand ascore, plus the resolvedwindowalongside the page.
GET https://fomo402.xyz/v1/leaderboard?metric=conviction&since=-24h&limit=25
GET https://fomo402.xyz/v1/actors/cooker
GET https://fomo402.xyz/v1/actors/cooker/calls?minUsd=1000&sort=ts&limit=500
GET https://fomo402.xyz/v1/tokens/EZdUAtouHwHKVM42r6Q7QnhGZp4zQfZ9ckXvDMkWpumpResponse shapes
One record, in full. Every field marked nullable does come back null upstream.
{
"id": "f8a2…", // upstream row id, stable
"seq": 481203, // monotonic per ingest, unique
"kind": "call", // "call" | "trade"
"closed": false, // author closed the position out
"ts": 1726543210000, // upstream creation time
"indexedAt": 1726543211884,// when this API first saw it
"views": ["latest", "conviction"],
"token": {
"mint": "EZdUAto…pump", "symbol": "RUNNER", "chain": "solana",
"mcapUsd": 412000, "buyers": 184, "buys": 291, "sells": 63,
"mcapChangePct": 38.2
},
"actor": {
"id": "u_931", "handle": "cooker", "name": "cooker",
"avatar": "https://…", "followers": 41200, "following": 310,
"isDev": false, "x": "https://x.com/…", "wallet": "9f2…"
},
"trade": {
"side": "buy", "usd": 8400, "sol": 41.2,
"pnlUsd": null, "pnlPct": null, "holdingsUsd": 8400
},
"conviction": { "buys": 3, "usd": 24100, "heldHours": 19.4 },
"text": "adding here, float is tiny",
"likes": 62
}- trade is null on callouts with no fill attached; conviction is null unless the row arrived through the conviction view.
- views lists every upstream view the row appeared in, merged, so filtering by view after the merge still works.
- actor.id and actor.handle are null for raw on-chain wallets. Filter them out with
hasAccount=true.
Errors
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_query | A parameter did not parse. The offending name is in param. You were not charged. |
| 402 | — | Payment required, rejected, or failed to settle. Body carries accepts and the reason. You were not charged. |
| 404 | not_found | No such id, handle or mint in the index. Common on a cold start; check /v1/stats. |
| 500 | internal_error | Our fault. Settlement does not run on a failed body, so nothing is charged. |
Discovery
An agent that only knows the domain can price the whole surface without spending anything:
GET https://fomo402.xyz/.well-known/x402 # price list, mint, network, payTo, feePayer, facilitator
GET https://fomo402.xyz/v1/schema # OpenAPI 3.1, price per operation under x-402
GET https://fomo402.xyz/v1/stats # index size, coverage window, poll health
GET https://fomo402.xyz/health # livenessThe OpenAPI document comes from the same price list and parameter table this page renders, so the two cannot drift.
Coverage
One poller runs against terminal.fomoscan.sh/api/feed/public/hydrate every 2 seconds, regardless of how many clients are reading. It dedupes rows by id across the three upstream views and updates an indexed row in place when its market cap, PnL or view tags change. Its seq and indexedAt stay put, so a keyset walk over it stays exact.
The index holds 200,000 rows; past that the oldest roll off. That window in hours depends on feed volume, and /v1/stats reports coverageHours live.
