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.
Coverage caveat. Upstream exposes only the most recent 40 rows per view. This service polls every 2s and keeps what rolls off, so history starts when the service did. Call /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 a 402 with the reason in error, and nothing is charged.
  • Serve. The server builds the body.
  • Settle. The payment settles on chain. A settlement failure downgrades the whole response to 402 rather than releasing the body.
  • Respond. 200 with the JSON, plus X-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.

This deployment runs in mock mode. In 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.002000

Once 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.

EndpointPriceAtomicWhat you get
/v1/feed$0.0022000Calls and trades in one stream
/v1/calls$0.0022000Callouts with a written thesis
/v1/trades$0.0022000On-chain buys and sells
/v1/search$0.0055000Full-text over thesis, ticker, mint, handle
/v1/records/{id}$0.0011000One record by id
/v1/actors/{handle}$0.0033000Actor profile with rollup stats
/v1/actors/{handle}/calls$0.0022000Everything one actor has posted
/v1/actors$0.0055000Actor directory, ranked
/v1/tokens/{mint}$0.0033000Token rollup: buyers, volume, callers
/v1/tokens/{mint}/calls$0.0022000Everything said about one mint
/v1/tokens$0.0055000Token directory, ranked
/v1/leaderboard$0.0110000Actors ranked by conviction, volume or PnL
/v1/statsfree0Index size, poll health, coverage window
/v1/schemafree0OpenAPI 3.1 document
/.well-known/x402free0x402 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.

ParameterTypeMeaning
kindstringcall or trade. Pinned on /v1/calls and /v1/trades. e.g. call
viewcsvUpstream views the row appeared in: latest, trending, conviction. e.g. trending,conviction
sidestringbuy or sell. e.g. buy
handlecsvOnly these handles. Leading @ optional, case-insensitive. e.g. cooker,ansem
excludeHandlecsvDrop these handles. e.g. spamdev
mintcsvOnly these contract addresses. e.g. EZdUAto…pump
symbolcsvOnly these tickers. Leading $ optional. e.g. $wif,bonk
chainstringNetwork id as upstream reports it. e.g. solana
qstringSearch terms. Every term must appear in thesis, ticker, mint, handle or name. e.g. runner low float
minFollowersintegerAuthor follower floor. e.g. 5000
maxFollowersintegerAuthor follower ceiling, for finding early accounts. e.g. 50000
minUsdnumberMinimum trade or callout size in USD. e.g. 2500
maxUsdnumberMaximum trade size in USD.
minMcapnumberMarket cap floor at the time of the row. e.g. 250000
maxMcapnumberMarket cap ceiling. e.g. 5000000
minHoldingsUsdnumberMinimum position still held.
minConvictionUsdnumberMinimum total the author has bought of this token.
minBuyersintegerToken traction floor: distinct buyers.
minPnlPctnumberMinimum realized PnL percent on the row.
minLikesintegerMinimum likes on the callout.
hasTextbooleanRows with (or without) a written thesis.
hasAccountbooleanRows from a FOMO account, versus raw wallets. e.g. true
hasWalletbooleanRows carrying a wallet address.
closedbooleanCalls the author has since closed out.
hideDevsbooleanDrop rows whose author is flagged as the token dev.
sincetimeLower time bound. Epoch ms, epoch seconds, ISO date, or an offset like -6h. e.g. -6h
untiltimeUpper time bound, same formats as since.

Shape and paging

ParameterTypeMeaning
sortstringts (default), usd, mcap, followers, pnl, likes, holdings, conviction.
orderstringdesc (default) or asc.
limitintegerRows per page, 1–500. Defaults to 50.
cursorstringOpaque cursor from pagination.nextCursor. Never build one by hand.
Absent numbers fail floors. A row with no USD amount does not pass 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.

EndpointPriceNotes
/v1/feed$0.002Calls and trades in one stream
/v1/calls$0.002Callouts with a written thesis
/v1/trades$0.002On-chain buys and sells
/v1/search$0.005Full-text over thesis, ticker, mint, handle
/v1/records/{id}$0.001One 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=500

Actors & 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.

EndpointPriceNotes
/v1/actors$0.005Actor directory, ranked
/v1/actors/{handle}$0.003Actor profile with rollup stats
/v1/actors/{handle}/calls$0.002Everything one actor has posted
/v1/tokens$0.005Token directory, ranked
/v1/tokens/{mint}$0.003Token rollup: buyers, volume, callers
/v1/tokens/{mint}/calls$0.002Everything said about one mint
/v1/leaderboard$0.01Actors ranked by conviction, volume or PnL
  • /v1/actors and /v1/tokens take sort, q, limit, cursor, plus minFollowers and minCallers respectively. 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 (default conviction) and since (default -24h), and returns each row with a rank and a score, plus the resolved window alongside 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/EZdUAtouHwHKVM42r6Q7QnhGZp4zQfZ9ckXvDMkWpump

Response 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

StatuserrorMeaning
400invalid_queryA parameter did not parse. The offending name is in param. You were not charged.
402Payment required, rejected, or failed to settle. Body carries accepts and the reason. You were not charged.
404not_foundNo such id, handle or mint in the index. Common on a cold start; check /v1/stats.
500internal_errorOur 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             # liveness

The 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.

This service republishes data from a public endpoint, unmodified apart from normalization. Nothing here is financial advice, and it claims no upstream affiliation.