Checking service status…

// developer reference

API Reference

The independent FOMO Family API. Resolve a fomo.family trader to available on-chain wallets and request FOMO-reported PnL, holdings, leaderboards, theses, and realtime activity over REST and WebSocket.

Last reviewed September 23, 2026. FOMO API is an independent, unofficial developer product and is not affiliated with fomo.family.

Get an API key →

Introduction

Every endpoint is a plain GET that returns JSON. No SDK required. Responses are UTF-8 JSON with permissive CORS, so you can call the API from a browser, a server, or a bot.

Base URL

https://api.fomoapi.io

All endpoints are served over HTTPS with permissive CORS, so you can call the API from a browser, server, or bot.

For AI & tooling: a machine-readable OpenAPI 3.1 contract is at /openapi.json, a concise product/API index at /llms.txt, the dated editorial catalog at /llms-full.txt, and the live self-describing catalog at GET /v1. Use the OpenAPI contract and this reference ahead of older article examples.

Authentication

Pass your API key as a Bearer token. A key is required for all data endpoints (trader resolution, trades, balances, holders, theses, search) - they return 401 without one. Keyless works only for discovery (/v1, /health, /status, /api); the leaderboard and /v2/alerts require a key too.

authorization: Bearer YOUR_API_KEY
TierAuthAccess
KeylessnoneDiscovery and meta only (/health, /status, /v1, /api). Every data endpoint needs a key
Free keyBearer keyEvery endpoint + the app feed stream /ws/alerts realtime for 7 days, then delayed 15s - 250,000 credits / month. The on-chain stream /ws/trades is Growth and up
PaidBearer key2,500,000 credits/mo (Starter) up to 112,500,000 (Scale) on a card subscription that renews monthly, plus sales tax or VAT where it applies. All paid plans keep /ws/alerts realtime; /ws/trades and OHLCV candles start at Growth - see pricing

A free key unlocks the full dataset - per-trader wallets, PnL, trades, holdings, theses - and realtime streaming. Plans differ by how many credits you get, not which data you can reach. A free key takes seconds to create and unlocks every endpoint; only /health, /status and /v1 answer without one.

Billing is usage-based credits, weighted by value. A leaderboard or normal call is 250 credits; the /v2/alerts feed is 125 credits; a direct call (search, trades, balances, token holders) is 250 credits; two calls cost more - a thesis call is 1,250 credits and a wallet resolution (/v2/users/{handle}) is 2,500 credits; the following pull is 250 credits. A free key gets 250,000 credits/month (about 100 wallet resolutions, 200 theses, or 1,000 normal calls) - a real taste, then it hits the wall. Free also drops to a 15s-delayed WebSocket after the first 7 days. Starter ($49.99) is the production floor with 2,500,000 credits and a permanently realtime stream. Builder ($199.99) is the same with 12,500,000 credits. Growth ($599) adds the on-chain stream, OHLCV candles and 37,500,000 credits with 3 concurrent streams; Scale ($1,500) is 112,500,000 credits and everything. Out of credits → 402; upgrade by card, which renews monthly (plus sales tax or VAT where it applies), or top up in USDC anytime.

Create a free key in one step: sign in to the dashboard (email code, no password) and your key is minted automatically. You can rotate it anytime. Buying a plan needs no account at all - the pricing page takes an email, sends you to checkout, and the key is created from that email and emailed to you when the payment clears.

Credits & headers

Keyed requests are metered in credits (not a request quota); every response carries:

HeaderMeaning
x-credits-costCredits this call cost (250 for a leaderboard or ordinary read, 125 for /v2/alerts, 1,250 per thesis page, 2,500 for wallet resolution, and 250 per following page that is walked)
x-credits-remainingCredits left on your key (monthly bucket + prepaid)
x-ratelimit-*Present on unauthenticated requests to the open discovery endpoints, which are capped per minute per IP

Billing is usage-based credits (weighted; a leaderboard or ordinary read is 250 credits and /v2/alerts is 125; every HTTP data endpoint requires a key). Every authenticated response carries x-credits-remaining and x-credits-cost; check /v2/me for your balance. When you run out of credits, paid endpoints return 402:

{
  "error": "credits_exhausted",
  "plan": "free",
  "remaining": 0,
  "message": "Out of credits. Pay by card to upgrade your monthly bucket, or top up in USDC: https://fomoapi.io/pricing",
  "upgradeUrl": "https://fomoapi.io/pricing"
}

Move to a bigger plan - see plans →, billed by card and renewing monthly, with sales tax or VAT added at checkout where it applies - or buy prepaid credits in USDC (POST /pay/create {plan:"credits", amount}). See Plans & higher limits for the tiers.

Errors

Errors are JSON with an error field and a matching HTTP status.

StatusMeaning
200OK
400Bad request (e.g. invalid window)
401Invalid or missing API key
402Out of credits (credits_exhausted) - upgrade by card, or top up in USDC
404Not found (unknown endpoint or handle)
429Rate limit exceeded (per-minute IP cap on the open discovery endpoints)

Quickstart

$ curl https://api.fomoapi.io/v2/leaderboard/24h \
    -H "authorization: Bearer YOUR_API_KEY"
const res = await fetch(
  "https://api.fomoapi.io/v2/leaderboard/24h",
  { headers: { authorization: "Bearer YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.traders);
import requests
r = requests.get(
    "https://api.fomoapi.io/v2/leaderboard/24h",
    headers={"authorization": "Bearer YOUR_API_KEY"},
)
print(r.json()["traders"])

Get API info

GET/v1

Returns the API description, your tier, rate limit, dataset size, and the endpoint list. Handy for a health/status probe with metadata.

{
  "name": "FOMO API",
  "version": "0.1.0",
  "tier": "free",
  "rateLimit": "60 requests / minute per IP",
  "dataset": { "source": "fomo", "traders": 12 },
  "endpoints": { … }
}

Health check

GET/health

Liveness probe. Returns ok, the loaded trader count, and process uptime.

{ "ok": true, "traders": 12, "uptime": 1834.2 }

Service status

GET/status

Check this before debugging your own integration. It tells you whether an error is ours. /health only says this process is alive, which is almost never the problem; /status reports what you actually experience. Keyless, free, and cached for 15 seconds.

{
  "status": "operational",
  "updatedAt": "2026-10-08T17:34:07.116Z",
  "components": {
    "api":         { "status": "operational" },
    "liveData":    { "status": "operational", "successRate": 1 },
    "alertStream": { "status": "operational", "lastEventSecondsAgo": 1 }
  }
}
Fields
NameValuesDescription
statusoperational · degraded · downThe worst of the components below
components.liveDatastatus, successRateLive trader lookups, judged on a 5 minute success rate. Degraded means calls are failing or slow; cached responses still serve
components.alertStreamstatus, lastEventSecondsAgoThe alert feed, judged on silence. Normal is roughly 5 events a minute with gaps under 65s, so minutes of quiet means the pipeline has stopped rather than the market being slow
components.onchainStreamstatus, plansThe on-chain /ws/trades feed, available on Growth and Scale. Independent of alertStream: one can be down while the other serves normally
notestringOnly present when not operational: what is affected, and what still works

Always returns HTTP 200, even when degraded. It reports a status rather than having one, so a monitor that treats a 503 here as "status page down" learns nothing. Point your uptime checker at the status field instead. /v2/status returns the same payload.

Leaderboard

GET/v2/leaderboard/{window}

Ranked traders for a time window. Each row carries both on-chain wallets, PnL, volume, holdings and userId - the exact shape your app renders. Key on userId, not the handle: it is FOMO's stable id for that trader, it survives a rename, and it drops straight into the /v2/users/{...}/ sub-resources.

Path parameters
NameValuesDescription
window24h · 7d · 30d · allRanking window
Query parameters
NameTypeDescription
limitint (1-150)Max rows. FOMO caps the board at 150 (100 on all); defaults to the full set.
Example
$ curl https://api.fomoapi.io/v2/leaderboard/24h?limit=2 \
    -H "authorization: Bearer YOUR_API_KEY"
Response
{
  "window": "24h",
  "source": "fomo",
  "capturedAt": "2026-08-25T18:04:00Z",
  "count": 2,
  "traders": [
    {
      "rank": 1,
      "handle": "CryptoKaleo",
      "userId": "1f08e6ab-5c73-5443-9225-bfc496cde51f",
      "displayName": "K A L E O",
      "pnlUsd": 151383,
      "volumeUsd": 119165,
      "trades": 73,
      "followers": 19967,
      "holdings": 4,
      "wallets": {
        "solana": "5AhfPStn66hRYoNNDfJHSDgCH7fBbwMQZUECRrhTo62F",
        "evm": "0x7b4d16237683fe1765e727eadf99c6f02adf0b59"
      },
      "topTokens": [ "0x7fe995", "0x51fb76" ],
      "verified": true
    }
  ]
}
Trader fields
FieldTypeDescription
rankintPosition in this window
handlestringSocial handle (use with /v2/users)
displayNamestringDisplay name
pnlUsdnumberRealized PnL in USD for the selected window
volumeUsdnumberTrading volume in USD
tradesintTrade count
followersintFollower count
holdingsintNumber of tokens currently held
wallets.solanastringSolana wallet address
wallets.evmstringEVM wallet address
topTokensstring[]Top held token identifiers
verifiedboolVerification flag exactly as fomo.family reports it; false when they report none

Token boards

The token boards FOMO users are actually trading. Three boards:

GET/v2/leaderboard/tokens/trending
GET/v2/leaderboard/tokens/most-held
GET/v2/leaderboard/tokens/graduated

trending is what FOMO users are actively trading right now, most-held is the tokens the most users are holding (FOMO's board is 25), and graduated is FOMO's graduated-token board. Each takes ?limit=N and returns { board, capturedAt, count, tokens }, where each token is { rank, image, token{name,symbol,address}, holders, network, priceUsd, change24h, marketCapUsd }. rank is the token's current position on FOMO's board (1 = top); we mirror FOMO's ordering exactly. A board returns { "available": false } when it is not available yet.

These are live. All three boards are pulled from FOMO on every call (behind a 5-minute cache), so stale is false and ageHours is 0 in normal operation. Each response carries source: live-fomo when it came straight from FOMO, or captured when FOMO did not answer and we served the last good board instead - that is the only case where stale can be true, so check source before concluding a board is broken. Two field caveats, both upstream rather than ours: holders is present on trending and graduated but null on most-held, because FOMO does not send it on that board; and fomoBuyers is no longer returned - FOMO has never populated it on these boards, so it was a guaranteed null on every row and the honeypot filter resting on it excluded nothing. To mean anything it would have to be computed from our own trade data.

Reading the boards. most-held is legitimately dominated by large caps - it answers which tokens the most people hold, which is a different question from what is moving. For what is actually being traded right now use trending, and for early-stage names use graduated, whose tokens are small by nature (typically well under $1M market cap).

Example
$ curl https://api.fomoapi.io/v2/leaderboard/tokens/trending?limit=5 \
    -H "authorization: Bearer YOUR_API_KEY"

Resolve a trader

GET/v2/users/{handle}

Resolve a single handle to everything we know about that trader in one call - around 35 fields. The handle is case-insensitive; a leading @ is allowed.

Counts and flags are read live from FOMO on every request (behind a short cache), not served from a stored snapshot. So followers, trades, swapCount, volumeUsd and verified are current rather than hours old. If the live read fails we return null for those fields rather than a stale value - we would rather tell you we do not know than hand you a number that is wrong.

Example
$ curl https://api.fomoapi.io/v2/users/CryptoKaleo \
    -H "authorization: Bearer YOUR_API_KEY"
Response
{
  "handle": "CryptoKaleo",
  "displayName": "K A L E O",
  "pnlUsd": 151383,
  "pnl": { "24h": 8231, "7d": 54120, "30d": 142900, "all": 151383 },
  "volumeUsd": 119165,
  "trades": 73,
  "followers": 19967,
  "holdings": 4,
  "wallets": {
    "solana": "5AhfPStn66hRYoNNDfJHSDgCH7fBbwMQZUECRrhTo62F",
    "evm": "0x7b4d16237683fe1765e727eadf99c6f02adf0b59"
  },
  "profile": {
    "followers": 19967,
    "averageHoldTimeSeconds": 63613,
    "fomoCreatedAt": "2025-09-03T23:25:33.441Z",
    "accountAgeDays": 360
  },
  "numTrades": 73,
  "swapCount": 184,
  "totalVolume": 119165.42,
  "following": 91,
  "averageHoldTimeSeconds": 63613,
  "createdAt": "2025-09-03T23:25:33.441Z",
  "accountAgeDays": 360,
  "description": "trader bio",
  "clan": { "id": "659b...", "name": "Ender", "icon": "...", "role": "member" },
  "activated": false, "private": false, "isRestricted": false, "isReferred": true,
  "verified": false
}

Also returned: userId, userHandle, profilePictureLink, coverPhotoLink, thumbhash, topTokens[], friendsFollowing and numFriendsFollowing.

accountAgeDays and createdAt give the trader's FOMO account age - useful for spotting fresh accounts vs established traders. averageHoldTimeSeconds is their typical hold duration, and clan is the group they trade with. wallets are the trader's REAL main wallets - FOMO exposes only a throwaway signer that holds nothing, which we resolve past.

Returns 404 {"error":"trader not found"} if the handle isn't in the dataset.

Trade history & holdings copy-trading

Two per-trader endpoints for building copy-trading bots. Both are populated per trader when nothing is available for that trader yet, these return { "available": false } with an empty array, so check that field.

Handle or userId - both work. Every /v2/users/{handle}/... sub-resource also accepts FOMO's userId in place of the handle: /v2/users/254245a7-575a-51be-9bc3-090a924789eb/positions is as valid as /v2/users/starcatcher444/positions, and both return identical data. That covers /trades, /positions, /swaps, /balances, /followers, /following and /spotlight. Prefer the userId if you store one: a handle can be renamed by its owner and the id cannot - our own directory holds one trader under both gebuxihuanheifu and laifu against a single id. The id form is also marginally faster, because the handle form spends a lookup arriving at exactly that id. Same credit cost either way. userIds come back on /v2/leaderboard/{window}, /v2/search, /v2/alerts and the /ws/alerts stream. The one exception is the bare /v2/users/{handle}, where a userId is served by /v2/users/id/{userId} instead.

GET/v2/users/{handle}/positionsolder names: /v2/users/{handle}/trades · /trades?user=

Use /positions. The route returns positions - a token aggregated into one row with an averaged entry and exit - so it was renamed. Both older names still answer and are not going away, but /v2/users/{handle}/trades now responds with deprecation: true and a link header naming /positions as its successor. For the individual fills rather than aggregated positions, use /v2/users/{handle}/swaps.

A trader's recent trades - captured FOMO history (open/closed positions with entry, exit and realized/unrealized PnL). Returns { "available": false } with an empty array when nothing is available for that trader yet, so check that field. Use ?limit=N.

FieldTypeMeaning
tradeIdstringFOMO trade id
tokenobject{ symbol, address }
statusstringopen or closed
amountnumbertoken size held / traded
avgEntryPrice / avgExitPricenumberaverage entry / exit (USD). avgEntryPrice is null exactly when the position was never bought - see the note below
realizedPnlUsd / unrealizedPnlUsdnumberclosed / open PnL (USD)
costBasisUsdnumberwhat the position cost (USD). Counts transfers in as well as buys
boughtAmount / soldAmountnumbertokens actually bought and sold - real swaps
transferredInAmount / transferredOutAmountnumbertokens that arrived or left without being traded (an airdrop, a wallet move)
avgTransferInPrice / avgTransferOutPricenumberaverage price at transfer (USD)
priceUsdnumberthe token's current price, so an open position can be marked without a second call
createdAt / closedAtstringISO timestamps

Reading a position correctly. amount reconciles as boughtAmount - soldAmount + transferredInAmount - transferredOutAmount (149 of 150 positions across 6 traders; the one exception was a rebasing token, off by 1.4%). Keeping a real buy separate from a transfer in is what makes an amount that otherwise looks wrong add up - and it explains the nulls: every one of the 76 null-avgEntryPrice rows in that sample had boughtAmount: 0 and a non-zero transferredInAmount. A null entry price means the trader received those tokens rather than buying them, so there is no entry to report. It is not a gap in the data. Where there is a buy, costBasisUsd / boughtAmount gives you entry.

?cursor=  older trades, page by page

The plain call returns every OPEN position and only the 25 most recent CLOSED trades. To go further back, page it. Start with ?cursor=start, then pass the nextCursor from each response back as ?cursor=, and keep going while nextCursor is not null. 25 closed trades per page, one upstream call and 250 credits each.

GET /v2/users/{handle}/trades?cursor=start
GET /v2/users/{handle}/trades?cursor=<nextCursor>

The first page carries the open positions as well as the first 25 closed; every page after it is closed trades only. Measured on a trader with 983 closed trades: 7 pages returned 175 closed trades with no duplicates, reaching five days back. Sort by timestamp yourself rather than relying on the order, because closed trades interleave across chains and are not strictly ordered across page boundaries.

Most other paging names do nothing here: lastId, offset, page, before and the rest are accepted and silently ignored, returning the identical first page. cursor is the one that works.

?deep=1  breadth in a single call

Two of their real parameters are a way through. Those 25 are counted per chain, and the two orderBy values return disjoint sets. So ?deep fans out across chains and both sort orders and merges the result. Measured: one trader went from 91 closed trades to 190, another from nothing at all to 127.

What it costs. One credit per upstream call that actually answers. ?deep=1 is the full fan-out; ?deep=N buys only N calls. A partial fan-out costs less than a full one, and a handle FOMO does not recognise costs a single credit rather than the whole quote. The plain call is unchanged at 250 credits.

Read this before you build on it. A complete trade history is not obtainable from FOMO at any setting. A trader with ten thousand trades cannot be enumerated, because the depth simply is not offered. The response never pretends otherwise: closedTotalOnFomo is FOMO's own total so you can see what fraction you are holding, and complete is always false. If you need the whole history, take the wallets from /v2/users/{handle} and read the chain directly.

FieldTypeMeaning
closedTotalOnFomonumberFOMO's real closed-trade count, so you know what share you have
perChainClosedTotalobjectthat total broken down by networkId
upstreamCalls / upstreamAttemptednumbercalls that answered, and calls tried. You pay for the first number
partialbooltrue when some chains did not answer in time. Ask again for the rest
completeboolalways false, for the reason above
Example
$ curl "https://api.fomoapi.io/v2/users/theveeman/trades?deep=1&limit=500" \
    -H "authorization: Bearer YOUR_API_KEY"

GET/v2/users/{handle}/balances

A trader's entire multi-chain portfolio in one call - not Solana-only. Holdings come back across Robinhood Chain (4663), Solana, Ethereum, Base, BSC and Monad together, so a six-figure Robinhood position sits next to a Solana one in the same response. Returns holdings, totalValueUsd across every chain, a byChain breakdown, and when FOMO exposes them, portfolio-level otherPnl, livePerpPnl and hyperliquidPerps. Add ?chain=robinhood|solana|base|bsc|eth|monad|arc|hyperliquid to narrow to one chain instead of making a second call.

FieldTypeMeaning
tokenobject{ symbol, address, networkId }
chainstringfriendly chain name: robinhood, solana, base, bsc, ethereum
amountnumbertokens held
priceUsd / valueUsdnumberunit price and position value (USD)
change24hnumber24h price move (%)
totalValueUsdnumbersum of all holdings across every chain (portfolio value)
byChainobjectper-chain rollup, e.g. { robinhood:{holdings,valueUsd}, solana:{...} }
livePerpPnl / hyperliquidPerpsobjectlive perp PnL / positions, when present

GET/v2/users/{handle}/following

Who a trader watches. Read live from FOMO, so it is the current list, not a snapshot. This is the discovery endpoint: work backwards from someone who is up, and you get the accounts they chose to follow, each already annotated with their own followers, trade count, lifetime volume and 24h PnL. Sort that list by pnl24h or volumeUsd and you have a shortlist of traders you had no other way to find.

Bounded at the first 300 names. That is up to six pages of 50, and we walk them for you inside the one request, paced on purpose so we are never hammering FOMO on your behalf. ?limit=N returns fewer.

One honest caveat: FOMO's own endpoint stops at 200 names per trader, whatever page size or cursor we ask with. So for a trader who follows more than that you get 200 names and truncated: true, and note tells you it was FOMO's wall rather than ours. We still ask for 300, so if FOMO lifts that limit you get the extra names with no change on your side. Either way truncated means the same thing: this is not the whole list.

Because those pages are read live, a slow one is possible. When that happens we return the names we already have with partial: true and retryable: true instead of holding your request open until it times out. Ask again a few seconds later and the walk resumes from where it stopped rather than starting over, so a second call finishes the list and you keep the names from the first. complete is true only when the list is whole, so it is the one field to check if you need certainty.

Wallets are not in this payload. Take any handle from the list and resolve it with GET /v2/users/{handle} when you want its addresses.

FieldTypeMeaning
handle / displayNamestringthe followed trader; handle is what every other endpoint takes
followers / followingnumbertheir own social counts
trades / swapCountnumberlifetime trade and swap counts
volumeUsdnumberlifetime traded volume (USD)
pnl24hnumber24h PnL (USD)
verified / privatebooleanFOMO verification, and whether the profile is private
twitterstringlinked X profile, when they have one
bio / avatarstringprofile description and picture
createdAt / accountAgeDaysstring / numberFOMO signup time and age in days
truncated / capboolean / numbertop level: whether the list was cut (at our 300 bound or FOMO's 200 wall), and what our bound is
completebooleantop level: true only when this is the trader's whole following list
partial / retryablebooleantop level, only on a slow pull: we stopped early, ask again for the rest

When it cannot answer. A handle FOMO does not know is a 404. A real trader whose live read did not finish in time is a 503 with retryable: true, not a 404, so you can tell "no such trader" from "try again". Neither costs any credits.

Credits. This one reads live from FOMO, but FOMO hard-stops at 200 names per trader and a single call already reaches that, so it is a flat 250 credits. A handle FOMO does not recognise, and a read that fails, cost nothing. Every response carries x-credits-cost and x-credits-remaining; out of credits returns a 402.

GET/v2/users/{handle}/followers

Who follows a trader. The inverse of /following, read live from FOMO, and every person comes back in the exact same shape, so the two routes drop into the same parser. Following tells you whose calls a trader watches; followers tells you who is watching theirs, which is how you size real backing rather than a number on a profile. Forty followers who each trade six figures is a different signal from forty empty accounts, and this endpoint hands you each follower's own volumeUsd, trades, pnl24h and account age so you can tell them apart.

One honest caveat, and it is a hard one. FOMO serves at most 200 followers per trader here, and offers no cursor past them. We tried: page size is ignored and the lastId cursor does not advance, so a four page walk returns the same 200 people four times. That means this is a sample of a trader's followers, not the full list. frankdegods has 249,483 followers and this returns 200 of them. sourceCapped: true and a plain English note say so on every response, so you are never guessing which side of the wall you are on. ?limit=N returns fewer; asking for more does not produce more.

Wallets are not in this payload. Take any handle from the list and resolve it with GET /v2/users/{handle} when you want its addresses.

FieldTypeMeaning
handle / displayNamestringthe follower; handle is what every other endpoint takes
followers / followingnumbertheir own social counts
trades / swapCountnumberlifetime trade and swap counts
volumeUsdnumberlifetime traded volume (USD)
pnl24hnumber24h PnL (USD)
clanobjecttheir clan {id,name,icon,role}, or null
verified / privatebooleanFOMO verification, and whether the profile is private
twitterstringlinked X profile, when they have one
bio / avatarstringprofile description and picture
createdAt / accountAgeDaysstring / numberFOMO signup time and age in days
sourceCapped / noteboolean / stringtop level: true plus an explanation whenever you are looking at FOMO's 200 wall
count / capnumbertop level: how many came back, and the ceiling we asked with

Credits. A flat 250 credits, same as /following: one call already reaches everything FOMO will give. A handle FOMO does not recognise is a 404, a live read that does not finish in time is a 503 with retryable: true, and neither costs anything.

GET/v2/users/{handle}/spotlight

FOMO's own pick of a trader's best work. Not our ranking, theirs: bestTrades is what FOMO surfaces as this trader's strongest positions, and bestTheses is the trades whose written thesis drew the most likes. It is the fastest way to judge a trader you have just found without reading their whole history, because it hands you the trade and the reasoning together, with entry, exit and both realized and unrealized PnL attached. 250 credits.

FieldTypeMeaning
bestTrades / bestThesesarrayFOMO's picks; the same item shape in both
tradeIdstringpass to /v2/trades/{tradeId} or its /comments
token / chainobject / stringwhat was traded and where
avgEntryPrice / avgExitPricenumberaverage in and out
realizedPnlUsd / unrealizedPnlUsdnumberclosed and open PnL on the position
thesis / thesisLikesstring / numberwhat they wrote, and how it landed
openedAt / closedAtstringwhen the position opened and closed; closedAt null means still open

GET/v2/users/id/{userId}

The inverse of /v2/users/{handle}. Our thesis, alert, holder and comment payloads all emit a userId, and until now there was no way to turn one back into a person. Same identity payload as the handle route, wallets included.

This costs 2,500 credits and counts against your monthly wallet-resolution allowance, exactly like /v2/users/{handle}. It answers the same question from the other direction and returns the same addresses, so it carries the same price. Pricing it at 250 credits would have made it a cheaper back door to the same data.

Copy-trading flow: /v2/leaderboard/{window} to find who to copy (each row carries userId, so key on that rather than the handle) → /v2/users/{userId}/trades to evaluate their entry/exit/PnL → /v2/users/{handle}/balances to mirror positions → the app feed stream WSS /ws/alerts to act when they trade, or the on-chain stream WSS /ws/trades (Growth and Scale) to act ahead of the app.

Trade detail

GET/v2/trades/{tradeId}

The full detail of a single trade: the individual swaps and transfers that make it up, average entry and exit, realized PnL, whether the trader is the token dev, and timestamps. Served from our captured store when we hold the trade and read live from FOMO when we do not, so a trade that exists now answers instead of returning { "available": false }. A live answer is marked source: "live-fomo" and adds the token's current price and liquidity, the position's status, and the trader's written thesis with its like count.

Bought is not the same as received. boughtAmount (FOMO's sumSwapOpen) is a real purchase; transferredInAmount is tokens that arrived without being bought, an airdrop or a wallet move. costBasisUsd counts transfers, so a position that looks bought may have been sent. The two are separate fields here for that reason.

FieldTypeMeaning
tradeIdstringFOMO trade id
traderstringhandle who made the trade
swaps / transfersarraythe on-chain legs of the trade
avgEntryPrice / avgExitPricenumberaverage entry / exit (USD)
realizedPnlUsdnumberrealized PnL (USD)
isDevboolwhether the trader is the token's dev

GET/v2/trades/{tradeId}/comments

The thesis thread on one trade. We print replies: N in several payloads and until now there was no way to read them. Every comment comes back with its text, author, like count, and parentId, which is set on a reply and null on a top-level thesis, so you can rebuild the thread rather than a flat list. FOMO's own olderThesisCount and newerThesisCount tell you how many theses that trader wrote on the same position before and after this one, which is how you catch someone quietly changing their mind.

Authors arrive as a bare userId with no handle attached. That is FOMO's shape, not a gap on our side; resolve one with GET /v2/users/id/{userId}. Takes ?limit=N up to 200. 250 credits.

FieldTypeMeaning
textstringthe comment itself
userIdstringwho wrote it; resolve with /v2/users/id/{userId}
likesnumberhow many people liked it
parentId / isReplystring / boolthe comment this replies to; null and false on a top-level thesis
olderThesisCount / newerThesisCountnumbertheses the same trader wrote on this position before and after
linksarraylinks embedded in the comment, each {text, link, provider}

Token holders smart money

GET/token/{address}/holders

Go from a token to the tracked traders who hold it, a smart-money holder signal instead of an anonymous holder list. Takes ?limit=N. Each holder: { handle, amount, valueUsd, priceUsd }. Populated from captured balances.

Example
$ curl https://api.fomoapi.io/token/So111...112/holders \
    -H "authorization: Bearer YOUR_API_KEY"

GET/v2/token/{address}/devs

The deployer and the insiders, with their own thesis attached. This is the cheapest rug signal FOMO has. A deployer quietly closing his own bag shows up here before it shows up in the price, and each dev row carries the thesis that person wrote about the token they made, which is the half nobody else sells. Every position comes with cost basis, average entry, realized and unrealized PnL, so you can see whether the dev is up and getting out or still holding.

One honest caveat: an empty list means FOMO knows of no dev holding, not that the token is safe. Absence of a signal is not a clean bill of health, and the payload says so rather than letting an empty array read as reassurance. Needs ?networkId= for a token outside our directory (4663 Robinhood Chain, 1399811149 Solana). 250 credits.

FieldTypeMeaning
handle / walletstring / objectthe dev, and their Solana and EVM addresses
isDevboolFOMO's own dev flag for this holder
amount / valueUsdnumbertokens held and what they are worth
costBasisUsd / averageEntryPricenumberwhat the position cost them
realizedPnlUsd / unrealizedPnlUsdnumbertaken off the table, and still on it
thesisstringthe dev's own written thesis on their own token

GET/v2/token/{address}/stats

Flow, not price. Candles tell you what the price did. This tells you who was pushing it: buys and sells, distinct buyers and sellers, and dollar volume on each side, over four windows at once (5m, 1h, 4h, 24h). netVolumeUsd and buySellRatio are computed for you so you are not deriving them on every row. A coin whose price is flat while net flow is deeply negative is a different coin from one that is flat on no volume.

top10HoldersPercent sits next to it, which is the concentration number that actually matters for supply control. Needs ?networkId= for a token outside our directory. 250 credits.

FieldTypeMeaning
holdersnumbertotal holder count
top10HoldersPercentnumbershare of supply in the top ten wallets
windowsobject{5m, 1h, 4h, 24h}, each the block below
buys / sellsnumbertrade counts in the window
uniqueBuyers / uniqueSellersnumberdistinct wallets on each side
buyVolumeUsd / sellVolumeUsdnumberdollar volume each way
netVolumeUsdnumberbuy minus sell; positive means bought into
buySellRationumberbuys per sell; null rather than infinity when there were no sells

GET/v2/tokens/activity

Several traders hitting the same coin at once, with the names. One call for the whole platform, no per-coin parameter. Each event says how many distinct traders took part, inside what window, for how much, and lists the traders FOMO considers the notable ones.

Read this caveat before you build on it. The board is FOMO's, not ours, and FOMO stopped publishing multi-user events on 2026-08-23. We pass through what it serves rather than hiding an empty board, and every response carries newestEventAt, newestEventAgeHours and, once past six hours, stale: true with a note. It is still worth having because our own firehose carries no multi-user events at all, so this is the only place the signal exists. For flow that is current to the minute use /v2/alerts or WSS /ws/alerts. Takes ?side=buy|sell, ?minTraders=N and ?limit=N up to 100. 250 credits.

FieldTypeMeaning
sidestringbuy or sell
uniqueTraders / tradesnumberhow many distinct people, and how many trades
windowMinutesnumberthe window they did it in
volumeUsd / priceChangePercentnumbersize of the move and what the price did
tradersarraythe notable participants, each {handle, displayName, userId, avatar}
topTradersInvolvedboolFOMO's own flag for "these were top traders, not random wallets"
stale / newestEventAgeHoursbool / numbertop level: whether the board has gone quiet, and by how long

GET/token/{address}/holders

Which of the traders we track hold a given token, ranked by USD value: a smart-money holder signal. Each holder is {handle, amount, valueUsd, priceUsd}.

Theses the "why"

The theses traders wrote about a coin - the reasoning behind the trades, not just the numbers. Returns available:false until a coin has data).

Every thesis is real, captured from the trader who wrote it, tied to the coin and the trade behind it. This is the reasoning behind the numbers, which most trader data leaves out entirely.

GET/v2/thesis/token/{mint}alias /token/{mint}/theses

Query: ?limit=50 (1–200), ?network={sol|bnb|base|eth|arc}, ?sort=likes|recent, ?threshold=10 (minimum position size in USD, default 0), ?pages=1-10. Arc is network id 5042. Each thesis:

Set your own minimum position size with ?threshold=. A thesis is written on a position, and threshold is the smallest position (in USD) a thesis must sit on to be returned. The default is 0: every thesis, exactly as FOMO's own app shows them, including small early buyers on a fresh low-cap coin. Raise it to cut out dust, e.g. ?threshold=10 skips theses on positions under $10 and ?threshold=1000 keeps only four-figure positions. ?minPositionUsd= is accepted as an alias. The value you used is echoed back as threshold in the response.
curl -H "authorization: Bearer $KEY" \
  "https://api.fomoapi.io/v2/thesis/token/Fu2oZoGxFtCDp29NKA4A89xcn255khq9xbxG7Mmtpump?threshold=10"
Theses accumulate, so this is the coin's whole thesis history, not just the newest few. ?sort=likes returns the most-liked theses on that coin, which is the crowd telling you which calls it actually agreed with. ?pages=1-10 walks FOMO's cursor for more history: one page is 25 theses, so ?pages=4 returns about 100. Every request is a live pull from FOMO (nothing is served from a cache), and each page costs 1,250 credits. Also ?sort=recent, ?sort=pnl and ?minLikes=5. This route carries likes but not equity, so ?sort=equity and ?minEquity do nothing here; use GET /v2/thesis for those. The two are complementary.
FieldTypeMeaning
textstringthe written thesis
id / tradeIdstring | nullstable thesis dedupe key / related position id for joins
handle / namestringtrader handle / display name
userIdstringtrader id
avatarstring | nullauthor profile-picture URL when FOMO or our resident directory has one; no user lookup is made per card
tokenImagestring | nulltoken-image URL when supplied or already cached; no token lookup is made per card
equitynumberthe trader's position value (USD)
tradeUsdnumbersize of the trade (USD)
isDevboolwhether the author is the token dev
likesnumberlikes on the thesis (what ?sort=likes ranks by)
repliesnumberreplies on the thesis
tsstringtimestamp

Alongside theses, the response carries: threshold (the minimum position size applied), capturedAt (when this was pulled), totalAvailable (FOMO's own count for the coin), and source: "live-fomo" for a live pull. On a coin whose buyers all hold small positions, theses are assembled from the coin's holders and marked via: "holders"; partial: true means some holders' older theses are still being fetched, so ask again a few seconds later for the full list. If the live pull cannot finish in time you get our last stored snapshot marked source: "snapshot", stale: true and ageSeconds; retry for live data.

GET/v2/thesis

Query: ?sort=recent|equity|pnl and ?minEquity=100000 to keep only theses backed by a six-figure position. This feed carries equity and PnL but not likes, so ?sort=likes and ?minLikes do nothing here.

Recent theses across all coins. Every row carries stable id, related tradeId when supplied, nullable avatar/tokenImage, numeric networkId, and a friendly chain name. Add ?chain=robinhood (or solana, base, bsc, eth, arc, or a raw chain id) to return only that chain's theses. Arc is 5042; Robinhood Chain (4663) is currently the largest share of FOMO thesis volume.

GET/v2/thesis/user/{id}

Every thesis written by one trader, newest first. {id} is a handle or a fomo userId. Rows use the same stable id, tradeId, nullable avatar, and nullable tokenImage fields as the global and token feeds. Add /token/{address} to filter to one trader's theses about a single token, or ?chain=arc (Arc = 5042) to a single chain. ?sort=likes gives that trader's most-liked calls, ?sort=recent the newest. Indexed from the same feed, so it grows as the feed runs.

Realtime streams

Two WebSockets. Both stream FOMO trades: who bought or sold, which token and contract, on which chain, at what size. They differ in where the trade comes from and how early you get it.

Feed view /ws/alertsOn-chain /ws/trades
What it isFOMO's live feed, exactly as people see it in the appEvery FOMO trade read straight off the chain
When you get itThe same moment the app shows itAbout 3.5 seconds before the app shows it
What it carriesBuys, sells and theses, plus mobile push alerts (price moves, listings, milestones)Trades only, with the real fill size, transaction hash and block
ChainsRobinhood Chain, Solana, Base, BSC, Ethereum, Monad, Arc, plus Hyperliquid perpsRobinhood Chain, Solana
PlansEvery plan (realtime on paid plans, 7-day trial on free)Growth and Scale
Reach for it whenYou want to react to what the crowd is seeing, or you need theses and push alertsBeing first is the point: copy-trading, sniping, front-running the alert

App feed stream WSS wss://api.fomoapi.io/ws/alerts

FOMO's trade data as the app publishes it. Every buy, sell and thesis from the feed, streamed at the same moment FOMO users receive them. Each message tells you the trader, the token and its contract address, the chain, the side and the size, so you can act on the exact token without a lookup. A typical buy:

{
  "type": "alert",
  "alertType": "buy",           // buy | sell | thesis | transfer | perp | listing | milestone | ...
  "source": "feed",             // "feed" (activity feed) or "push" (mobile notification)
  "eventId": "149318d0-70af-4acb-a607-b126dd4db4a3",   // FOMO's stable event id (dedupe key)
  "userId": "6dcf7c78-2537-522a-8307-3f9970c081be",   // the trader, survives a handle rename
  "tradeId": "a323b5ca-c769-4b07-a422-833c4cafbe1f",  // the position this event is about
  "swapId": null,               // null on buy/sell; set only when FOMO sends one
  "transferId": null,
  "trader": "frankdegods",
  "token": "PONS",
  "tokenAddress": "0x39dbed3a...",
  "chainId": 4663,
  "chain": "robinhood",
  "usdValue": 4000,
  "text": "frankdegods bought $PONS ($4K size)",
  "ts": 1788378000000
}

Connect with your key and you are streaming. On connect you get a {type:"welcome", realtime, delaySeconds} message and a short replay of recent alerts, then live {type:"alert"} messages. source is "feed" on everything arriving today; the "push" source came from an off-device mobile capture that is no longer running, so filtering for it returns nothing.

The ids are how you join. trader is a handle, and a handle can be renamed by its owner, so it is not a key. Every feed alert therefore also carries FOMO's own ids: userId goes straight into GET /v2/users/{userId}/positions, and tradeId into GET /v2/trades/{tradeId}, with no name lookup in between. eventId is stable per event and is the right dedupe key. Measured on the live stream, 30 of 30 feed alerts carried userId and tradeId; swapId and transferId are null on buy/sell events and are passed through only when FOMO sends them. Push-sourced alerts (source:"push") carry none of these ids, because FOMO's notifications do not include them.

const ws = new WebSocket("wss://api.fomoapi.io/ws/alerts?key=YOUR_API_KEY");
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.type === "alert" && (m.alertType === "buy" || m.alertType === "sell"))
    console.log(m.trader, m.alertType, m.token, m.usdValue, "on", m.chain);
};

Plans. Realtime on any paid plan (?key=YOUR_API_KEY). A free key is realtime for 7 days, then 15 seconds delayed; no key at all is 60 seconds delayed, as a demo. Every tier gets every event with the same fields; a plan only buys freshness. One connection counts as one request and the messages are free, which is why a socket beats polling /v2/alerts.

Follow one trader (copy-trading)

For a copy-trading product you usually want a socket that streams a single trader's trades and nothing else. Subscribe two ways:

1. On connect, with a query param:

new WebSocket("wss://api.fomoapi.io/ws/alerts?trader=frankdegods");
// only that trader's buys, sells and theses arrive

2. At runtime, send a subscribe message (switch traders without reconnecting):

ws.send(JSON.stringify({ action: "subscribe", trader: "frankdegods" }));
// server replies { type: "subscribed", filter: { trader: "frankdegods" } }
ws.send(JSON.stringify({ action: "unsubscribe" }));  // back to the full firehose

On subscribing you also get an immediate replay of that trader's recent matching alerts. Thesis alerts include stable eventId/tradeId plus nullable avatar and tokenImage URLs; a null means FOMO did not supply the image and it was not already cached, never that the API issued a per-card lookup. Filters combine: ?trader=, ?token= (symbol or contract address), ?chain= (robinhood, solana, base, bsc, eth, arc, monad, hyperliquid, or a raw chain id), ?source= (feed|push), ?type= (buy|sell|thesis|whale|price|trade). Arc is chain id 5042. No filter means the full stream. Pair the per-trader stream with GET /v2/users/{handle} (their real wallet) and /v2/thesis/user/{id} (their reasoning) for a complete copy-trading pipeline.

Subscribe to one chain

Every alert carries the exact token traded: token (symbol), tokenAddress (the contract address), chainId, and a friendly chain name (e.g. "robinhood" for chain id 4663). To stream a single chain, filter by it:

new WebSocket("wss://api.fomoapi.io/ws/alerts?chain=robinhood");
// only Robinhood Chain (4663) trades - currently FOMO's most active chain.
// Also: solana, base, bsc, eth, arc (5042), polygon, arbitrum, or a raw chainId.
// robinhood, hood and rh all map to 4663. Combine, e.g. one trader on one chain:
//   ?trader=frankdegods&chain=robinhood
// or at runtime:
ws.send(JSON.stringify({ action: "subscribe", chain: "robinhood" }));

Each {type:"alert"} message includes tokenAddress, chainId and chain so you can act on the exact token on the exact chain without resolving the symbol or memorizing chain numbers.

GET/v2/alerts

The full activity firehose as a REST fallback for the stream: every buy, sell and thesis, newest first. Thesis alerts include nullable avatar and tokenImage URLs without a per-event lookup. ?limit=N, ?type= (whale, thesis, price, trade, follow, buy, sell), ?chain= (robinhood, solana, base, bsc, eth, arc, monad, hyperliquid, or a raw chain id), ?since= (ISO timestamp). Every item includes source, chainId and a friendly chain name. Arc is 5042.

On-chain stream Growth

WSSwss://api.fomoapi.io/ws/trades

Every FOMO trade, read straight off the chain instead of from the app's feed, about 3.5 seconds before it appears in the app (measured p50 on production 2026-09-29; two independent methods gave 2.2s and 3.5s). The bigger edge is that it carries every fill, while the app feed emits only trades above roughly $3,000 of position value. This is the alpha: the window to act before the crowd sees the alert. Chains: Robinhood Chain (chain=robinhood) and Solana (chain=solana). Nothing is stored; a new connection replays the last 50 trades, then goes live.

Which stream? On-chain when you need to be first. The feed view stream when you want exactly what people receive in the app, at the same time they receive it, or when you need theses.

const url = "wss://api.fomoapi.io/ws/trades";
const ws = new WebSocket(url + "?key=YOUR_API_KEY&chain=robinhood&minUsd=500");
ws.onmessage = (e) => {
  const t = JSON.parse(e.data);
  if (t.type !== "trade" || t.replay) return;
  console.log(t.side, t.token.symbol, t.usdValue, t.trader.handle ?? t.trader.wallet, t.latencyMs + "ms after the block");
};

Filters, on the URL or via {"action":"subscribe", ...}: chain, trader (username or wallet), token (contract), minUsd, side. Message shape:

{ "type":"trade", "chain":"robinhood", "chainId":4663, "side":"buy",
  "trader":{"wallet":"0x…","handle":"cosekant"},
  "token":{"address":"0x…","symbol":"CACHE","mcap":1234567},
  "usdValue":3871.2, "amountToken":12345.6, "txHash":"0x…", "block":52795900,
  "blockTs":1788378000000, "seenAt":1788378001100, "latencyMs":1100, "source":"onchain",
  "verified":"relay" }

verified says how we know the trade is FOMO's: db (the wallet is a resolved FOMO trader), relay (confirmed against the routing layer FOMO trades pass through), or code (shape rule only, while confirmation is pending for a never-seen wallet). In the rare case a pending wallet turns out to belong to another app, a {"type":"retract","id":...} follows for what was already sent.

Access: a Growth or Scale API key, passed as ?key=. Any other key, or no key, is closed with code 1008 and the reason in the close message. The connection counts as one request; the events are free.

Trading accounts

A trading account is separate from a data API subscription. Use the API key attached to your trading account with authorization: Bearer YOUR_API_KEY.

GET /v2/trading/account returns your account status. GET /v2/trading/docs returns the full account documentation; both require a valid key with a trading account and remain available while that account is provisioning or its hosting has lapsed.

Active, hosting-paid accounts can use GET /v2/trading/balances, GET /v2/trading/positions, POST /v2/trading/buy, and POST /v2/trading/sell. Read your account documentation before submitting orders. See trading account setup for availability.

Your wallet's private key

To request your trading wallet's private key, contact support on Telegram. This is a support request, not a public API endpoint. Anyone with the private key can control the wallet; keep it private.

Plans & credits

Every HTTP data endpoint is available to a free key (250,000 credits/month, no card); a free key gets 7 days of the realtime /ws/alerts WebSocket, then a 15s-delayed feed. Billing is usage-based credits weighted by value: a leaderboard or ordinary read is 250 credits, a thesis is 1,250 credits per page, and a wallet resolution is 2,500 credits; /v2/alerts is 125 credits and the following pull is 250 credits per page. Plans raise your monthly credit bucket and keep /ws/alerts realtime. They are card subscriptions that renew monthly, and renewing early extends the plan you have rather than resetting it. A plan gates two things outright: the on-chain stream /ws/trades and OHLCV candles, both Growth and Scale only - not Free, not Starter.

PlanCredits / monthBillingRealtime WebSocketOn-chain + OHLCV
Free250,000no card7 days realtime, then 15s delayed-
Starter - $49.99/mo2,500,000card, monthlyrealtime-
Builder - $199.99/mo12,500,000card, monthlyrealtime-
Growth - $599/mo37,500,000card, monthlyrealtimeYes
Scale - $1,500/mo112,500,000card, monthlyrealtime · multi-streamYes

Out of credits, or need always-on streaming for a production bot? Upgrade or top up →

Paid plans are card subscriptions billed on a hosted checkout, renewing monthly until you cancel. Card prices are before sales tax or VAT, which is added at checkout where your location requires it. Buying needs no account: type an email on the pricing page and the key is created from it and emailed to you. You can also pay once in USDC on Solana - no card, no KYC - but a USDC payment buys one month and does not renew. The complete FOMO wallet dataset is available to license separately - inquire. For enterprise volume, redistribution, or a private feed, contact us.