{
  "name": "FOMO API",
  "description": "Social trading data API — verified wallets, real PnL, live data.",
  "version": "0.2.0",
  "auth": {
    "required": "All data endpoints (traders, trades, balances, holders, theses, search) require an API key. THE DOCUMENTED DEFAULT is `authorization: Bearer YOUR_API_KEY`. ALSO ACCEPTED, because many clients and generated SDKs send one of these by default and a valid key in an unread header used to come back as a bare 401 \"invalid API key\": `x-api-key: YOUR_API_KEY`, `?key=` (also `?apiKey=`, `?api_key=`), and an Authorization header carrying the bare key with no \"Bearer \" prefix. All four are equivalent - same key, same credits, same limits. A 401 now names the place we read your key from, so \"wrong header\" and \"wrong key\" are told apart without asking us.",
    "keyless": "Only /v1, /health and /api answer without a key (in any of the accepted places above). Every data endpoint returns 401 without one - the old keyless showcase was closed after 93% of alert traffic and 84% of leaderboard traffic arrived with no key at all.",
    "websocket": "TWO SEPARATE STREAMS, do not confuse them. BOTH COST ZERO CREDITS - the connection, every message and every reconnect are free on every plan; only REST calls are billed. (1) APP FEED STREAM wss://api.fomoapi.io/ws/alerts (?key=YOUR_API_KEY): mirrors the FOMO app, arriving when the app shows it. On EVERY plan - realtime on any PAID plan (no request counter can demote a paid key to the delayed feed), a FREE key is realtime for its first 7 days then delayed 15s, keyless is delayed 60s. (2) ON-CHAIN STREAM wss://api.fomoapi.io/ws/trades (?key=YOUR_API_KEY): the same trades read straight off the chain, landing ~3.5s BEFORE the app feed (measured p50; the ~15s once claimed here did not reproduce) AND carrying every fill, not just the large ones. GROWTH or SCALE only. \"realtime WebSocket\" on the Starter plan means (1), not (2).",
    "getKey": "Free key (250,000 credits/month = 1,000 ordinary 250-credit reads) at https://fomoapi.io/dashboard. Add a card from the dashboard with no subscription and no charge, and a free key gets 500,000 bonus credits (once per card). Paid plans are CARD subscriptions on a hosted checkout that renew monthly and carry +20% monthly credits over the base allowance (the one-month USDC plan gets the base allowance), and buying one needs NO ACCOUNT: the pricing page takes an email, sends you straight to checkout, and the key is created from that email and emailed to you. Card credit packs (one-time, never expire): $10 = 250,000, $25 = 650,000, $50 = 1,400,000, $100 = 3,000,000 credits. With a card saved you can turn on auto top-up on the dashboard: when your balance drops below a threshold you choose, we charge the saved card for the pack you picked (at most 3 times a day). Renewing early EXTENDS the expiry you already have rather than resetting it. UNUSED PAID CREDITS ROLL OVER: whenever a paid bucket refreshes (renewal, early or on time, or the start of a calendar month) its unused remainder moves to your prepaid balance, which never expires, so you never lose credits you paid for. Free-key credits do not roll over. Upgrade at https://fomoapi.io/pricing. Secondary rails, both optional: prepaid USDC credits, and a one-month plan paid in USDC on Solana (no card, no KYC) that does not renew."
  },
  "pricing": {
    "model": "Usage-based credits, weighted by value. 250 credits = 1 upstream FOMO call for ordinary reads (search, trades, balances, token holders). Leaderboards are 250 credits; the /v2/alerts feed is 125. Two calls cost more: the handle→wallet resolution is 2,500 credits and a thesis call is 1,250 per page. A handle we cannot resolve costs only 250, not 2,500. Following is billed by the pages we actually walk (~50 names/page, up to ~4 for a full 200-name pull), 250 each. You get a monthly credit bucket by plan, refilled each billing period; outgrow it and the normal move is up a tier by card, or you can buy prepaid USDC credits. Out of credits → paid endpoints return 402.",
    "tiers": "Free 250,000 credits/mo = 1,000 calls (taste everything) · Starter $49.99/mo (2,500,000 credits = 10,000 calls) · Builder $199.99/mo (12,500,000 credits = 50,000 calls) · Growth $599/mo (37,500,000 credits = 150,000 calls; adds the on-chain /ws/trades stream and OHLCV candles) · Scale $1,500/mo (112,500,000 credits = 450,000 calls). All billed BY CARD on a hosted checkout that renews monthly, with sales tax or VAT added on top at checkout where it applies; a one-month USDC on Solana payment (no card, no KYC) is also accepted but costs 20% more (Starter 59.99, Builder 239.99, Growth 719, Scale 1,800 USDC), gets the base credits without the card +20%, and does not renew.",
    "topUp": "Optional overage rail (most people just move up a tier by card): prepaid USDC credits at 25,000 credits per USDC, priced above the plan rate. Buy any dollar amount: POST /pay/create {plan:\"credits\", amount:<usdc>, email, keyId} → a Solana Pay USDC link; credits land on payment. No KYC, no card.",
    "creditCosts": {
      "leaderboard": 250,
      "alerts_feed": 125,
      "alerts_checkpoint_poll_with_nothing_new": 0,
      "not_modified_304": 0,
      "normal_direct_call": 250,
      "thesis_per_page": 1250,
      "wallet_resolution": 2500,
      "unresolved_handle_miss": 250,
      "following_per_page_walked": 250
    },
    "freeWhenNothingChanged": "Two ways to poll without paying for data you already have. (1) CONDITIONAL REQUESTS: the leaderboards, the three token boards, /v2/alerts, /v2/thesis, /v2/search, /v2/tokens/search and /v2/wallets/* send an ETag. Send it back as If-None-Match and, if the response would be byte-identical, you get 304 Not Modified with x-credits-cost: 0 and x-credits-remaining unchanged. A changed response is a normal 200 at the normal price, with the new ETag. (2) EMPTY ALERT POLLS: GET /v2/alerts with ?cursor= (or ?since=) that returns count:0 costs 0 credits. When it returns events it costs 125 as before. Per-trader routes that pull live from FOMO (positions, balances, swaps, holders, ...) do not offer free 304s, because answering them costs a live upstream call."
  },
  "rateLimit": "Free tier: 20 requests/minute per key (keyless discovery: 600/min per IP). Upgrade to a paid plan for higher throughput — paid usage is metered in CREDITS, not rate-capped, so you pay for exactly what you pull. FAILURE PAUSE: 25 failed calls in a row from one key to ONE endpoint pause that key on that endpoint for 60s (retry-after says how long, never billed). For a paid key only the caller's own 4xx count (answered 429 too_many_failures); upstream timeouts, wallets still resolving and available:false never pause a paid key. A free key also pauses on upstream failures, answered 503 upstream_degraded.",
  "dataset": {
    "source": "fomo",
    "traders": 8,
    "capturedAt": "2026-08-26T00:00:00Z"
  },
  "datasetLicensing": "THE COMPLETE FOMO DATASET — every fomo.family username mapped to its verified Solana + EVM wallet — is available to license as a one-off, separate from the API plans. Enterprise-grade and priced accordingly. Serious buyers only. Inquire on Telegram: https://t.me/eulatxt",
  "endpoints": {
    "GET /health": "liveness check",
    "GET /status": "SERVICE STATUS, keyless and free. Tells you whether a failure is OURS before you debug your own code. status is operational|degraded|down, with a component breakdown: api, liveData (live trader lookups, judged on a 5-minute success rate), alertStream (judged on how long since the last event; the capture normally runs ~5/min with gaps under ~65s) and onchainStream (the /ws/trades on-chain feed, Growth and Scale only). Always answers HTTP 200 even when degraded, because it reports a status rather than having one - a monitor that reads a 503 here as \"status page down\" learns nothing. Cached 15s. /v2/status is the same payload.",
    "GET /v2/leaderboard/{window}": "ranked traders (window: 24h|7d|30d|all). ?limit=1-150 (FOMO's ceiling measured per window: 150 on 24h/7d/30d, 100 on all), defaults to the full board, higher values clamp. Requires a key. 250 credits per call regardless of limit, so ask for the full 150 rather than paging. Responses carry an ETag: send it back as If-None-Match and an unchanged board answers 304 Not Modified at 0 credits. The board is cached for up to two minutes, so polling faster than that mostly returns 304s",
    "GET /v2/search?q=": "unified search over traders (handle/name → wallet, PnL) AND tokens (symbol/name → address, market cap). Each result has a `type`. ?type=traders|tokens|all, ?limit",
    "GET /v2/tokens/search?q=": "token-only search by symbol or name (e.g. PONS, ANSEM) → {symbol, address, name, image, marketCapUsd} (?limit)",
    "GET /v2/wallets/{handle}": "costs 2,500 credits on a hit, 250 on a miss. A trader's REAL Solana and EVM trading wallets from our store - the same proof rules as /v2/users/{handle}, so FOMO's published signer/decoy addresses are never returned. Returns {handle, found, userId, wallets{solana,evm}, verified{solana,evm}, complete}. Store-only and fast; for a trader with nothing on file, /v2/users/{handle} resolves live",
    "GET /v2/wallets/owner/{address}": "costs 2,500 credits on a hit, 250 on a miss, free for input that is not an address. Reverse lookup: the FOMO username a real Solana or EVM trading wallet belongs to. Decoy/signer addresses deliberately do not resolve. Returns {address, chain, found, handle, userId, verified, wallets}",
    "GET /v2/users/{handle}": "costs 2,500 credits on a hit and only 250 on a handle we cannot resolve. Resolve a trader: both wallets, PnL per window (pnl:{24h,7d,30d,all}; pnlUsd=all-time) and the trader's LIVE FOMO profile — userId, userHandle, displayName, description, avatar/profilePictureLink/thumbhash, coverPhotoLink, verified, followers, following, numTrades (=trades), swapCount, totalVolume (=volumeUsd), averageHoldTimeSeconds, createdAt + accountAgeDays (FOMO signup age), clan{id,name,icon,role}, isReferred, activated, private, isRestricted, followsCurrentUser, friendsFollowing + numFriendsFollowing. Counts and verified come straight from FOMO, not from the leaderboard snapshot",
    "GET /v2/users/{handle}/swaps?source=relay": "the ON-CHAIN source. Reads Relay, which is what FOMO bridges through, so one query covers EVERY chain (solana, robinhood, base, bsc, ethereum, arbitrum) instead of one explorer each. Each leg carries usd AT TRADE TIME and usdNow, so unrealised PnL is computable - FOMO gives neither. Keyed by the trader's VERIFIED evm wallet, so resolve /v2/users/{handle} first; a handle still resolving returns 409 retryable. Relay rate-limits, so a call walks a few pages and ACCUMULATES: keep calling while moreAvailable is true. Covers Relay-routed flow only - measured against FOMO on unipcs at 99/103 in-window (96%), the 4 misses being same-chain swaps that never touched Relay.",
    "GET /v2/users/{handle}/swaps": "a trader's individual FILLS, which /trades cannot give: /trades returns POSITIONS (a token aggregated into one row with an averaged entry and exit). Each swap: {swapId, chainId, chain, tokenIn{address,amount,usd}, tokenOut{address,amount,usd}, tradeIdIn, tradeIdOut, at}. tradeIdIn/tradeIdOut join back to /v2/users/{handle}/trades. ACCEPTS A FOMO userId IN PLACE OF THE HANDLE: /v2/users/{userId}/swaps works and is marginally faster, because the handle form spends a lookup arriving at exactly that id. Same credit cost. ?deep=1 goes deeper and ACCUMULATES: each call spends a small, reliable budget on filters not yet tried for that trader and merges the results, so calling it a few times converges (measured on unipcs: 208 -> 231 -> 243 -> 257 -> 259 swaps over 6 calls, reaching back from 2026-08-12 to 2026-07-28). filtersRemaining and moreAvailable tell you whether another call buys anything. Billed per upstream call that actually answered. ?cursor= PAGES THE REAL HISTORY: FOMO's cursor here is lastSwapId, re-probed 2026-09-17 (a page of 100 followed by 100 entirely new ones). Pass the response's nextCursor back as ?cursor= and keep going while it is not null. 100 fills per page, one upstream call each. 250 credits.",
    "GET /v2/users/{handle}/positions": "the clearer name for what /trades returns (positions, averaged entry/exit). Identical response and identical price; /trades stays as the legacy alias.",
    "GET /v2/users/{handle}/trades": "trade history: token, side, size, PnL, timestamps. ACCEPTS A FOMO userId IN PLACE OF THE HANDLE. Each row carries the full position economics: costBasisUsd, boughtAmount (sumSwapOpen, a real buy) and transferredInAmount (sumTransferIn, tokens that arrived without being bought), soldAmount, transferredOutAmount, priceUsd (current) and userId. avgEntryPrice is null EXACTLY when the position was never bought - measured across 150 positions on 6 traders, all 76 null-entry rows had boughtAmount 0 and a non-zero transferredInAmount, i.e. the tokens were received, not purchased. So a null is the truth rather than a gap, and these fields say which case you are in. Where there IS a buy, costBasisUsd / boughtAmount gives entry. amount reconciles as bought - sold + transferredIn - transferredOut (149/150 exact; the exception was a rebasing token off by 1.4%). source:\"captured\" = rich on-device history (entry/exit); source:\"feed\" = live recent trades indexed from the activity feed (any active trader, no capture needed). ?cursor= PAGES THE CLOSED HISTORY: FOMO's cursor is lastTradeId, re-probed 2026-09-17 on a trader with 983 closed trades (12 pages, 25 brand new rows every page, no repeats, reaching a week back and still going). Pass nextCursor back as ?cursor=. ?deep=1 instead fans out across chains and both sort orders in ONE call (25 -> 182 on one trader); use ?cursor for depth and ?deep for breadth in a hurry. Costs 250 credits per upstream call (?deep=1 = 12, or ?deep=N to buy fewer). ?status=closed|open narrows the page: normalizeFomoTrades returns opens before closed and limit caps at 200, so a trader with 200+ OPEN positions (unipcs has 271) fills the window and every closed trade falls off the end at any limit and any ?deep. ?status=closed is how you get the closed history you paid depth for. The response carries openCount, closedCount and truncated so you can see what was held versus returned. The response carries closedTotalOnFomo, perChainClosedTotal and complete:false so you always know what fraction you are holding. NOTE: a full trade history is NOT obtainable from FOMO at any setting",
    "GET /v2/users/{handle}/balances": "live portfolio: holdings (token, amount, price, USD value, 24h move) + totalValueUsd, plus otherPnl / otherPnlV2 / otherEquity / livePerpPnl / hyperliquidPerps / nativeEvmBalances when present. COMPLETENESS - read this before reconciling anything. Upstream caps this response at ~100 holdings and there is NO WAY PAST THE CAP: probed directly on 2026-09-29, ?limit, ?offset, ?page, ?pageSize and ?lastId are all IGNORED (byte-identical payload, same first row) and there is no paginate route (404). So every response carries complete/truncated plus upstreamRows: truncated:true means positions beyond the page exist and CANNOT be fetched at any price, and totalValueUsd is then a FLOOR, not the portfolio. Rows arrive roughly largest-USD-first but NOT strictly (one inversion measured across three portfolios), so the missing tail is probably - not provably - the smallest positions. WHAT totalValueUsd IS: the sum of holdings[].valueUsd in the payload you were served, and nothing else. It reconciles against summing the rows by construction, and it does NOT reconcile against the FOMO app portfolio figure. INCLUDED: stablecoin/cash rows (USDC was the single largest row on one sampled portfolio) and dust in full - 59 of 100 rows on one portfolio were worth under $1. EXCLUDED: otherEquity (equity upstream counts outside the rows - 0.06, 3,492.80 and 0.22 on the three sampled portfolios), livePerpPnl and hyperliquidPerps (one sample carried +79,215.60 of perp PnL: that is PnL and margin, not holdings), nativeEvmBalances (native gas balances, separate array), otherPnl/otherPnlV2 (PnL, never additive to a mark), and anything the cap truncated. No row is negative and none is netted out: measured 0 negative and 0 flagged outside upstream equity across 301 rows. ?chain=solana|robinhood|base|bsc|eth narrows the rows; count and totalValueUsd then describe the slice while truncated still describes the upstream page, and chainFiltered:true says so",
    "GET /v2/users/{handle}/followers": "who FOLLOWS this trader, the inverse of /following. Same shape per person: handle, displayName, followers, trades, volumeUsd, pnl24h, twitter, verified, account age. FOMO caps this at 200 per trader and offers NO cursor past them, so it is a sample of followers rather than the full list, and sourceCapped:true says when you are looking at the cap. 250 credits. Wallets are not in this payload, resolve one with /v2/users/{handle}",
    "GET /v2/users/{handle}/spotlight": "FOMO's OWN pick of a trader's best trades and best-received theses. bestTrades[] and bestTheses[] each carry token, entry/exit, realized and unrealized PnL, the written thesis and its likes. 250 credits",
    "GET /v2/users/id/{userId}": "the inverse of /v2/users/{handle}: turn a userId (which our thesis, alert and comment payloads all emit) back into a person, wallets included. Costs 2,500 credits and counts against the wallet-resolution allowance, exactly like /v2/users/{handle}, because it answers the same question. If FOMO does not answer in time and we already hold this trader's proven wallets, the answer comes from our store (source:\"store\", same wallets, live profile fields null) instead of a 503. A 503 is only returned when we have nothing on file, and it is not charged.",
    "GET /v2/users/{handle}/following": "the accounts this trader follows: handle, displayName, followers, trades, volumeUsd, pnl24h, twitter, clan, verified, account age. Live from FOMO. FOMO itself serves at most 200 names per trader no matter how deep you page, and truncated:true plus sourceCapped:true say so when you hit it; partial:true on a rare slow pull means ask again and the walk resumes. complete:true is only ever set with proof - an empty list counts as complete only when FOMO's own profile count (profileFollowing) is also 0. A busy upstream is a 503 with Retry-After, never a 404 and never billed; 404 means FOMO itself says the handle does not exist. Successful reads cost 250 credits per page (about 50 names), with at least one page charged. Cached responses retain the page-based price; limit only trims output, not pages billed. Read x-credits-cost for the actual charge. Wallets are not in this payload, resolve one with /v2/users/{handle}",
    "GET /trades?user={handle}": "alias of /v2/users/{handle}/trades",
    "GET /v2/trades/{tradeId}": "one trade in detail: swaps, transfers, avg entry/exit, realized PnL, isDev, timestamps",
    "GET /v2/trades/{tradeId}/comments": "the thesis THREAD on one trade: every comment with text, author userId, likes, parentId (a reply) and links, plus olderThesisCount/newerThesisCount. We print replies:N in several payloads; this reads them. 250 credits. Authors arrive as a bare userId, resolve one with /v2/users/id/{userId}",
    "GET /v2/thesis/token/{mint}": "costs 1,250 credits (as do all /v2/thesis endpoints) — theses on a coin, the *why* behind trades: {id, tradeId, text, handle, name, userId, likes, replies, equity, tradeUsd, isDev, ts, networkId, chain}. id is the stable thesis dedupe key; tradeId joins the position and WS thesis alert without another FOMO call. ?sort=likes returns the MOST-LIKED theses on that coin (what the crowd actually agreed with), ?sort=recent the newest, plus ?limit and ?network. Returns a bounded window of the coin's thesis history, not a guaranteed complete archive. ?sort=likes|recent|pnl and ?minLikes=5. NOTE: this route carries likes but NOT equity (the per-coin source does not return position size), so ?sort=equity and ?minEquity are no-ops here. Use /v2/thesis for equity. ?threshold=N only returns theses on positions worth at least $N (default 0 = no minimum position size; e.g. ?threshold=10 drops dust; this does not guarantee complete history or app parity). ?pages=N (1-10, default 1) requests more upstream history; recent probes returned 25 theses per page, but page size and completion can vary. ?limit=N defaults to 50 and caps the response at 200; use ?pages=4&limit=100 to request about 100 rows. Upstream timeouts and the current-holder fallback can return fewer rows. No fixed retention period or full-history guarantee is offered. Billed 1,250 credits per requested page. A live pull is attempted; a stored fallback is labeled source:snapshot and stale:true. theses on one coin: stable id, related tradeId when supplied, and nullable avatar/tokenImage URLs. A non-null visual is directly renderable from FOMO or a resident directory; no per-card lookup. ?network= accepts arc (5042), plus sort, limit, and pages (recent probes returned 25 theses per page). ?threshold=N returns only theses on positions worth at least $N: default 0 = no minimum position size; e.g. ?threshold=10 drops dust. Results remain bounded by pages, limit and upstream availability.",
    "GET /v2/thesis": "recent theses across all coins. Every row carries a stable id for dedupe and tradeId when FOMO supplied the related position; alert-ring rows reuse the WS eventId as id. Each also carries networkId + friendly chain. ?chain=robinhood|solana|base|bsc|eth returns only that chain (robinhood = 4663, the biggest thesis chain) Also ?sort=recent|equity|pnl and ?minEquity (e.g. ?minEquity=100000 for theses backed by a six-figure position). NOTE: this feed carries equity and PnL but NOT likes, so ?sort=likes and ?minLikes are no-ops here. Use /v2/thesis/token/{mint} for likes. The two sources are complementary. recent theses across coins: stable id, related tradeId, nullable avatar/tokenImage, networkId, and friendly chain. Filter ?chain=arc for Arc chainId 5042 (or any supported friendly/raw chain ID).",
    "GET /v2/thesis/user/{id}": "available theses by one trader (id = handle or FOMO userId), from comments on returned open positions and a bounded window of recent closed positions. This is not a complete thesis archive; increasing limit or changing sort does not retrieve older upstream pages. Every row carries a stable thesis id for dedupe and its tradeId for joining the position / WS alert. ?sort=likes for the most-liked available theses, ?sort=recent for newest, ?chain= to a single chain, ?limit. Each carries likes + replies ?sort=likes|recent|equity|pnl plus ?minEquity / ?minLikes. Each carries equity and realized/unrealized PnL on that coin. theses by handle or stable FOMO userId, with the same id, tradeId, nullable avatar, tokenImage, networkId, and chain fields. Filter ?chain=arc for Arc (5042).",
    "GET /v2/thesis/user/{id}/token/{address}": "one trader's available theses about one token, filtered from the same bounded position window as /v2/thesis/user/{id}; not a complete archive. Each carries stable id and related tradeId. Pulled live (one upstream call per trader, shared with /v2/thesis/user/{id} for 15 minutes). 1,250 credits when it returns theses. If the trader has none on that token you get available:false reason:\"no-data\", and if upstream did not answer you get 503 retryable. Neither is charged. one trader's theses about one token, with stable id, related tradeId, and nullable avatar/tokenImage URLs.",
    "GET /v2/token/{address}/devs": "the DEPLOYER and insider positions on a token, each with their own written thesis: {handle, wallet, isDev, amount, valueUsd, costBasisUsd, realizedPnlUsd, unrealizedPnlUsd, averageEntryPrice, thesis}. The cheapest rug signal FOMO has, a deployer closing his own bag shows up here first. Needs ?networkId= for a token outside our directory. 250 credits",
    "GET /v2/token/{address}/stats": "buy/sell FLOW and holder concentration, which candles do not tell you: windows{5m,1h,4h,24h} each with buys, sells, uniqueBuyers, uniqueSellers, buyVolumeUsd, sellVolumeUsd, netVolumeUsd and buySellRatio, plus holders and top10HoldersPercent. Needs ?networkId= for a token outside our directory. 250 credits",
    "GET /v2/token/{address}/warnings": "the coin warnings FOMO shows before a trade: buyDisabled, sellDisabled, tradingDisabled, allowlisted, severe, and warnings[] of {name, description, severity} (e.g. Unsellable token, Zero liquidity, Few holders, Unverified token). An empty list means FOMO raised no warning, not that the coin is safe. Needs ?networkId= for a token outside our directory. 250 credits",
    "GET /v2/tokens/activity": "FOMO's coordinated-flow board: several DISTINCT traders hitting the same coin inside a short window, with the names. Each event: side, token, windowMinutes, uniqueTraders, trades, volumeUsd, priceChangePercent, traders[]. NOT the same data as /v2/alerts, which carries no multi-user events. CAVEAT: this is FOMO's board and FOMO stopped publishing to it on 2026-08-23, so responses carry stale:true and newestEventAgeHours. ?side=buy|sell, ?minTraders=N. 250 credits",
    "GET /token/{address}/holders": "which tracked traders hold a token — smart-money holder signal: {handle, amount, valueUsd, priceUsd} (?limit)",
    "GET /v2/token/{address}/candles": "OHLCV candles down to 1-SECOND resolution (?resolution=1S|15S|30S|1|5|15|30|60|240|720|1D|7D, ?countBack, ?from, ?to, ?networkId, ?fill=1). GROWTH AND SCALE ONLY (any other plan gets 403 plan_required, free). Buckets with no trade are omitted unless ?fill=1. Bars are cached for one bar width (3s minimum, 60s maximum), so polling faster than that returns the same bars. When the chart datafeed is slow, the last good series for that token and resolution (up to 10 minutes old) is served with stale:true and staleAgeSec. Otherwise the answer is 503 retryable with retry-after, and it is not charged.",
    "GET /v2/leaderboard/tokens/trending": "what fomo users are actually trading (?limit). Successful reads cost 250 credits, except an unchanged board fetched with If-None-Match, which is a free 304. All three token boards use a process-shared 5-minute cache, refreshed by a request after expiry, not on a schedule or per key. `capturedAt` is our fetch time, not FOMO publication time or a board version. `source`:\"live-fomo\" includes in-memory cache hits; `source`:\"captured\" means the live pull failed and a stored board was served. Retain capturedAt, ageHours and stale when assessing freshness. CONDITIONAL REQUESTS ARE FREE: every board response carries an ETag; send it back as If-None-Match and an unchanged board answers 304 Not Modified at 0 credits (x-credits-cost: 0). Only a changed board costs 250. There is no board-update stream or webhook. Each token carries rank (its position in the served FOMO board), image, token{name,symbol,address}, holders, network, priceUsd, change24h, marketCapUsd, volume24hUsd. holders is null on most-held only, because FOMO does not send it on that board",
    "GET /v2/leaderboard/tokens/most-held": "the tokens the most fomo users are holding right now (FOMO's board is 25) — same shape as trending incl. rank (?limit). These are the most HELD tokens, so the list is legitimately dominated by large caps; that is the answer, not a stale board. `holders` is null here because FOMO does not send it on this board",
    "GET /v2/leaderboard/tokens/graduated": "FOMO's third token board: tokens that have GRADUATED. Small by nature, typically well under $1M market cap, which is what makes it the early-stage list rather than the large-cap one (?limit). Same shape as trending",
    "GET /v2/alerts": "recent activity, same events as WS /ws/alerts (?limit, ?type, ?since). TYPES YOU ACTUALLY GET, measured over 500 consecutive alerts on 2026-09-25: sell 45%, buy 34%, thesis 17%, perp 3%, listing <1%. transfer, milestone and recap are in the contract but effectively never arrive - they only ever came from captured mobile PUSH notifications, of which there are 50 in total against 26,562 feed events. It is also NOT a firehose: FOMO emits only LARGE_* events, so small trades never reach us. The floor MOVES and is FOMO's, not ours - re-measured 2026-10-27 over the 3,117 feed buy alerts in our whole retained window, the knee is near $1,400-1,500 of position value (6 of 3,117 below $1,400) and 29.9% of buys are below $2,950, where September measured a cliff near $3,000. Do not hard-code it. See WS /ws/alerts for the full SIZE FLOOR note. REST fallback for the live WS stream. Each item has source:\"feed\"|\"push\" and FOMO's own eventId/userId/tradeId/swapId/transferId. ?since= walks back through our ring, which holds more history than FOMO's own /feed does: FOMO serves at most 100 feed items (about 10 minutes) with no cursor behind them, so this stream IS the paging. PER-TRADER RECOVERY: ?trader=<handle> (case-insensitive; aliases ?user=/?handle=) or ?userId=<FOMO userId> - prefer userId, it survives a handle rename - plus ?token=<symbol|address> and ?source=feed|push, mirroring the websocket filters. This is how you recover ONLY the traders you follow instead of paying 125 credits a page for the whole firehose. CHECKPOINTING, and why ?since= alone is not enough: alert timestamps are NOT unique - measured over 200 consecutive live alerts, 25 distinct ts values were each shared by 2-3 events - and ?since= is INCLUSIVE (ts >= since), so resuming from the newest ts you saw RE-DELIVERS every event in that millisecond, and there is no ts that skips the duplicates without also skipping real events. Use the cursor instead: every response carries nextCursor, oldestCursor and hasMore. Store nextCursor; next poll with ?cursor=<it> returns only what is strictly newer. If hasMore is true the gap is larger than one page: repeat with ?before=<oldestCursor> (keeping your ?cursor= or ?since= as the floor) until hasMore goes false, then advance your checkpoint to the nextCursor from the FIRST page. Cursors are opaque \"<ts>.<id>\" - read them off a response, never build one; a malformed one is a 400. Ordering is ts descending then id descending, a total order, so page boundaries are stable. `id` is also the websocket dedupe key, so dedupe on it as a backstop. TIMESTAMPS: ts is FOMO's own event timestamp QUANTISED TO 5 SECONDS - 500 of 500 sampled buy/sell alerts were an exact multiple of 5000 ms - and it tracks EXECUTION rather than publication (median -878 ms from the block over 130 proven matches; the bucket is floored, hence the negative). It is not our delivery time and not precise to better than 5s, so never diff two alerts and call it a latency. See WS /ws/alerts for the measurement, and use execTs where present. PRICE: 125 credits per page, but a poll with ?cursor= or ?since= that returns count:0 (nothing new) is FREE, and responses carry an ETag: If-None-Match on an unchanged page answers 304 at 0 credits. So polling with a checkpoint only costs when there is something new. REST fallback for the app-feed WebSocket. LARGE-only: ?includeSmall=1 and ?minUsd= are accepted but currently DELIVER NOTHING (see the SMALL FILLS note above), so this is a large-trade feed with a ~$3,000 floor that is FOMO's own. For every fill with no floor use WS /ws/trades (Growth/Scale, solana+robinhood+bsc+base) or GET /v2/users/{handle}/swaps. Thesis alerts additionally carry nullable avatar/tokenImage URLs; ?chain=arc filters Arc chainId 5042. Recovery: ?trader=/?userId= to scope to the traders you follow, and ?cursor=<nextCursor> for an exactly-once checkpoint (?since= is inclusive and alert timestamps are not unique, so it re-delivers the boundary millisecond).",
    "WS /ws/alerts": "realtime stream. Connect via wss://api.fomoapi.io/ws/alerts; receive {type:\"welcome\"} then {type:\"alert\",...,token,tokenAddress,chainId,chain,source} live. Every alert also carries FOMO's own ids: eventId (stable per event, the dedupe key), userId (the trader, stable across a handle change), and tradeId/swapId/transferId identifying the object the event is about - so an alert joins directly to /v2/users/{userId}/positions and /v2/trades/{tradeId} with no name lookup. Measured over one feed page: userId present on ~99%, tradeId on ~98%; swapId and transferId are null on buy/sell events and are passed through only when FOMO sends them. Follow ONE trader with ?trader=<handle> or ?userId=<FOMO userId> (or {\"action\":\"subscribe\",...}) - prefer userId for a long-lived subscription, because a handle can be renamed and the userId cannot; filter by ?chain=robinhood|solana|base|bsc|eth|hyperliquid, ?token=<symbol|address>, ?source=, ?type=buy|sell|perp. Each alert carries the token CONTRACT (tokenAddress), chainId + friendly chain name (e.g. robinhood for chainId 4663). HYPERLIQUID PERPS stream here too: chain \"hyperliquid\" (chainId 1337), type \"perp\", token is the market symbol (HYPE, BTC, TAO and equity perps like NVDA) and tokenAddress is null because a perp has no contract. ?chain=hyperliquid (aliases hype, hl, perps) returns only those. SIZE FLOOR - the single most important thing to know about this stream: it is NOT every trade. FOMO's app feed only emits its own LARGE_* events, so small trades never reach us and we cannot invent them. THE FLOOR MOVES, SO DO NOT HARD-CODE IT - it is FOMO's, set upstream of our ingest, and it dropped by half between September and October without announcement. Re-measured 2026-10-27 over every feed buy alert in our retained window (n=3,117, 2026-10-24 to 10-27): the knee is near $1,400-1,500 of POSITION value, with only 6 of 3,117 buys below $1,400 but 29.9% below $2,950 and a minimum of $0.68 - so it is a soft knee with a thin tail under it, not a hard wall. This note said \"smallest buy $2,985, nothing below $2,950\" for a month on a 2026-09-25 sample of 500; that was true then and is now wrong by a factor of two, which is the point. Treat any number here as a measurement with a date on it. On buys usdValue equals positionValueUsd (3,117 of 3,117), so that is the field the floor applies to. Against FOMO's own per-token feed for one active coin in September, 86% of real buys and 88% of real sells sat under the then-$3,000 floor and none of them appeared here. The floor is FOMO's and it is upstream of our ingest - we apply no minimum of any kind, verified by injecting $1 and $0 events end to end. IT GATES THESIS EVENTS TOO, which is easy to miss because the number above is quoted in buys: a coin whose whole thesis population sits under the floor produces NO events here of any type. Measured 2026-09-30 on Fu2oZoGxFtCDp29NKA4A89xcn255khq9xbxG7Mmtpump - 25 theses over 25 hours, largest position $899, so below the 2026-10 floor as well as the $3,000 one in force when this was measured - ZERO alerts of any type in our whole 28,773-event ring, while GET /v2/thesis/token returned all 25. A control coin that DOES appear here (ANSEM) has 7 of its 25 theses at or above $3,000. So if you are watching a small-cap for theses, poll GET /v2/thesis/token/{mint}; the socket cannot carry what FOMO never publishes. So treat this as a LARGE-TRADE feed, not a firehose: do not compute market share, trade counts or volume from it. For complete per-token activity use GET /token/{addr}/... or /v2/users/{h}/swaps, which read the full data. MONEY FIELDS - tradeUsd is the size of this fill when supplied upstream or attached from a unique on-chain execution match; tradeUsdSource names the pricing source when provided. These fields are optional: a missing tradeUsd means size unknown, never zero. positionValueUsd on buys is the position mark AFTER the fill; on an add it is the whole position, so do not sum it as purchase volume. realizedPnlUsd on sells is signed and CUMULATIVE FOR THE POSITION, not the PnL of that sell. Take the last value per tradeId, or difference consecutive values within the same position. usdValue is a legacy compatibility field that may still contain the position mark or realized PnL even when tradeUsd is present; use the explicitly named fields rather than treating usdValue as a fill size. Perp and thesis alerts do not establish a fill size. Optional fill attribution and its partial coverage are described below. EXACT FILL - this now has a real answer, and the old one was not one. Joining on tradeId does NOT identify the fill: GET /v2/trades/{tradeId} for position 094dd0b3-22ff-41ba-b676-f093c8293f01 returns 2,243 swaps, so picking one by timestamp inside it is a guess. Instead, a buy/sell alert may now carry txHash (the exact on-chain execution), execTs (when the CHAIN saw it, in ms), execLagMs (ts - execTs, so you can see for yourself how far the event timestamp sits from the block - measured median 0 ms) and tradeUsd (the size of THAT fill, with tradeUsdSource naming the pricing) - matched against the on-chain stream on chain+token+side+trader inside a time window, and attached ONLY when exactly one execution matches. fillMatch says which case you are in: \"onchain-exact\" = proven, use it; \"ambiguous\" = two or more of this trader's executions on this token and side fall in the window (a split or repeatedly-added position), so fillCandidates and txHashCandidates list them and NOTHING is attached as the fill; field absent = no match, fields absent. We never pick a winner out of several. swapId stays null always: it is FOMO's own identifier and is not derivable from a chain, so we do not invent one. COVERAGE, and it is partial by construction: the on-chain stream covers solana, robinhood, bsc (BNB Chain, added 2026-10) and base (added 2026-10-28; the 71% figure that follows was measured over solana and robinhood alone - 71% of buy/sell alerts over 24h, n=6,653) and can only name a trader whose wallet we have proven (73% of those, so ~52% of all buy/sell alerts are eligible at all). TIMESTAMPS, and this has now been wrong twice - first as \"publish time\", then as \"whole-second\". `ts` is FOMO's own event timestamp QUANTISED TO FIVE SECONDS: measured 500 of 500 buy/sell alerts on 2026-09-29, every single ts was an exact multiple of 5000 ms, and 0 of 500 fell on an odd second. An earlier note here said one-second resolution, which was a smaller sample reading the same data less carefully. It tracks EXECUTION rather than publication - against 130 proven on-chain matches the median of ts minus block time is -878 ms - and the sign is the giveaway: the bucket is floored, so ts lands 0-5s BEFORE the true moment, which is exactly the small negative median you would predict. PRACTICAL CONSEQUENCE: never treat ts as precise to better than 5 seconds, never diff two alerts and call the result a latency, and do not sort on it alone - use the (ts, id) cursor, because a 5s bucket puts many events on the same value. What it is NOT: it is not our delivery time - the socket hands an event over roughly 1-6s AFTER its ts (median 5.75s over 36 samples) - and it is not the block, because the 5-second bucketing alone puts it up to five seconds off. So: use ts for ordering and checkpointing, and use execTs (milliseconds, the block) when present for \"when did this execute\". Never assume ts is the moment you received it. CREDITS: the websocket is free - the connection, every message and every reconnect cost ZERO credits, on every plan. Only REST calls are billed. PAID KEYS ARE NEVER DELAYED: any paid plan is realtime, with no per-request counter that can quietly demote you to the delayed feed. WALLET COVERAGE, stated plainly because copy-trading depends on it: we resolve ONE verified main wallet per chain per trader (mainSolana / mainEvm), not an exhaustive list of every address they control. A trader trading from a second address is not covered, and that is the main reason a covered-chain alert still comes back without a txHash SMALL FILLS ARE CURRENTLY UNAVAILABLE - ?includeSmall=1 and ?minUsd= ARE ACCEPTED BUT DELIVER NOTHING, and this note used to claim otherwise. No event has carried tier:\"small\" since that source stopped; measured 2026-10-07, a 75s sample with and without ?includeSmall=1 returned byte-identical output and zero tier:\"small\" events on both the socket and the /v2/alerts ring. The parameters stay accepted so existing code does not break, but DO NOT BUILD ON THEM - they are a no-op today. IF YOU NEED EVERY FILL WITH NO SIZE FLOOR, USE WS /ws/trades: it reads the chain directly and has no minimum of any kind. Measured 2026-10-07 over 90 seconds it carried 468 trades with a median of $10.71 and a minimum of $0.15, and 99% of its buys (222 of 225) were under $3,000, with a median trade of $10.71 - so the bulk of them sit under the app-feed floor in either its September or its October position, and are trades this stream cannot see. It needs a Growth or Scale key and covers solana, robinhood, bsc (BNB Chain) and base. For a specific trader on any chain, GET /v2/users/{handle}/swaps reads the full data with no floor. app-feed stream, LARGE trades only - see the SIZE FLOOR note above. Zero credits: connection, messages and reconnects are all free. `ts` is FOMO's own event timestamp, QUANTISED TO 5 SECONDS (500/500 sampled), tracking execution (median -878 ms from the block over 130 proven matches) - NOT your delivery time, and not precise to better than 5s; use execTs for the block. Thesis alerts include stable eventId, related tradeId when supplied, and nullable avatar/tokenImage URLs without per-event enrichment. Filter ?chain=arc for Arc (5042), ?type=thesis for thesis events, or use other friendly/raw chain IDs.",
    "GET /v2/me": "Your own account: current plan, your credits block (credits:{monthly, usedThisMonth, prepaid, remaining}), when the plan expires, and which of the two WebSockets your key includes (streams.appFeed / streams.onChain). If a trial or comp sits on top of a month you paid for, afterExpiry tells you the plan and date you drop back to. Does NOT consume any credits",
    "WS /ws/trades": "ON-CHAIN stream, a DIFFERENT stream from /ws/alerts. Same FOMO trades read straight off the chain rather than from the app feed, so they arrive EARLIER. MEASURED LEAD, and the ~15s this used to claim was wrong: two independent methods on 2026-09-29 put it at 2.2s (pairing 40 individual executions across both streams) and 3.5s (measuring each leg: on-chain delivery is block+1.37s p50 over ~800 frames, app-feed delivery is block+4.9s p50). So call it ~3.5s p50, a few seconds - not a quarter of a minute. THE BIGGER EDGE IS COMPLETENESS: the app feed emits only LARGE_* events with a hard floor near $3,000 of position value, and 86% of real buys / 88% of real sells fall under it. On-chain there is no floor. Requires a GROWTH or SCALE key: wss://api.fomoapi.io/ws/trades?key=YOUR_API_KEY&chain=robinhood. CHAINS: solana, robinhood, bsc (BNB Chain, chainId 56) and base (chainId 8453, added 2026-10-28) - filter with ?chain=solana|robinhood|bsc|base, or omit it for all four. Every frame has the same shape on every chain; usdSource names the quote leg (bsc: stable = USDC/USDT/USD1, wbnb = WBNB priced off DexScreener; base: stable = USDC/USDbC, weth = a WETH leg, ethWrap = the native ETH wrapped or unwrapped by FOMO's order contract, all priced off DexScreener). Starter and Free get /ws/alerts instead, which is realtime but only as early as the app itself"
  },
  "realtime": {
    "alerts": "wss://api.fomoapi.io/ws/alerts — app feed on every plan, LARGE trades only (FOMO emits no small ones); ZERO credits (connection, messages, reconnects); ts is FOMO's own event timestamp quantised to 5 SECONDS (tracks execution, median -878 ms from the block; not your delivery time and not precise to better than 5s - execTs is the block); thesis alerts include nullable avatar/tokenImage; filter Arc with ?chain=arc (5042)",
    "trades": "wss://api.fomoapi.io/ws/trades — on-chain stream, Growth and Scale. Arrives ~3.5s before the app (measured p50 2026-09-29, two methods: 2.2s paired / 3.5s per-leg; the old ~15s claim did not reproduce), and carries EVERY fill rather than only the large ones the app feed emits"
  },
  "note": "wallets are the trader's real main trading wallet, verified on chain: ONE verified main wallet per chain (mainSolana / mainEvm), NOT an exhaustive list of every address the trader controls - a second address they also trade from is not covered, and nothing here claims otherwise. wallets:{status:\"resolving\"} means the resolution is still running IN THE BACKGROUND and finishes whether or not you ask again - it is not a request to poll, and that response is REFUNDED (x-credits-cost 0), so a handle we cannot resolve yet is never billed as one we did. Most traders are resolved before anyone asks: every trader appearing on /ws/alerts is queued for resolution when we first see them trade. A handle we have genuinely never seen is the case that still returns \"resolving\", and the next call is typically the answer. holdingsStatus:\"resolving\" says the same thing about the live positions on an off-board lookup: the pull outran its budget and is still running, so `holdings` is null rather than empty - a trader who genuinely holds nothing carries no holdingsStatus at all. Realtime alerts stream at wss://api.fomoapi.io/ws/alerts — t.me/eulatxt.",
  "docs": "https://fomoapi.io/"
}