// 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.
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
| Tier | Auth | Access |
|---|---|---|
| Keyless | none | Discovery and meta only (/health, /status, /v1, /api). Every data endpoint needs a key |
| Free key | Bearer key | Every 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 |
| Paid | Bearer key | 2,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:
| Header | Meaning |
|---|---|
x-credits-cost | Credits 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-remaining | Credits 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.
| Status | Meaning |
|---|---|
200 | OK |
400 | Bad request (e.g. invalid window) |
401 | Invalid or missing API key |
402 | Out of credits (credits_exhausted) - upgrade by card, or top up in USDC |
404 | Not found (unknown endpoint or handle) |
429 | Rate 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
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
Liveness probe. Returns ok, the loaded trader count, and process uptime.
{ "ok": true, "traders": 12, "uptime": 1834.2 }
Service 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 } } }
| Name | Values | Description |
|---|---|---|
status | operational · degraded · down | The worst of the components below |
components.liveData | status, successRate | Live trader lookups, judged on a 5 minute success rate. Degraded means calls are failing or slow; cached responses still serve |
components.alertStream | status, lastEventSecondsAgo | The 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.onchainStream | status, plans | The on-chain /ws/trades feed, available on Growth and Scale. Independent of alertStream: one can be down while the other serves normally |
note | string | Only 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
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.
| Name | Values | Description |
|---|---|---|
window | 24h · 7d · 30d · all | Ranking window |
| Name | Type | Description |
|---|---|---|
limit | int (1-150) | Max rows. FOMO caps the board at 150 (100 on all); defaults to the full set. |
$ curl https://api.fomoapi.io/v2/leaderboard/24h?limit=2 \ -H "authorization: Bearer YOUR_API_KEY"
{ "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 } ] }
| Field | Type | Description |
|---|---|---|
rank | int | Position in this window |
handle | string | Social handle (use with /v2/users) |
displayName | string | Display name |
pnlUsd | number | Realized PnL in USD for the selected window |
volumeUsd | number | Trading volume in USD |
trades | int | Trade count |
followers | int | Follower count |
holdings | int | Number of tokens currently held |
wallets.solana | string | Solana wallet address |
wallets.evm | string | EVM wallet address |
topTokens | string[] | Top held token identifiers |
verified | bool | Verification flag exactly as fomo.family reports it; false when they report none |
Token boards
The token boards FOMO users are actually trading. Three boards:
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).
$ curl https://api.fomoapi.io/v2/leaderboard/tokens/trending?limit=5 \ -H "authorization: Bearer YOUR_API_KEY"
Resolve a trader
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.
$ curl https://api.fomoapi.io/v2/users/CryptoKaleo \ -H "authorization: Bearer YOUR_API_KEY"
{ "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.
| Field | Type | Meaning |
|---|---|---|
tradeId | string | FOMO trade id |
token | object | { symbol, address } |
status | string | open or closed |
amount | number | token size held / traded |
avgEntryPrice / avgExitPrice | number | average entry / exit (USD). avgEntryPrice is null exactly when the position was never bought - see the note below |
realizedPnlUsd / unrealizedPnlUsd | number | closed / open PnL (USD) |
costBasisUsd | number | what the position cost (USD). Counts transfers in as well as buys |
boughtAmount / soldAmount | number | tokens actually bought and sold - real swaps |
transferredInAmount / transferredOutAmount | number | tokens that arrived or left without being traded (an airdrop, a wallet move) |
avgTransferInPrice / avgTransferOutPrice | number | average price at transfer (USD) |
priceUsd | number | the token's current price, so an open position can be marked without a second call |
createdAt / closedAt | string | ISO 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.
| Field | Type | Meaning |
|---|---|---|
closedTotalOnFomo | number | FOMO's real closed-trade count, so you know what share you have |
perChainClosedTotal | object | that total broken down by networkId |
upstreamCalls / upstreamAttempted | number | calls that answered, and calls tried. You pay for the first number |
partial | bool | true when some chains did not answer in time. Ask again for the rest |
complete | bool | always false, for the reason above |
$ 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.
| Field | Type | Meaning |
|---|---|---|
token | object | { symbol, address, networkId } |
chain | string | friendly chain name: robinhood, solana, base, bsc, ethereum |
amount | number | tokens held |
priceUsd / valueUsd | number | unit price and position value (USD) |
change24h | number | 24h price move (%) |
totalValueUsd | number | sum of all holdings across every chain (portfolio value) |
byChain | object | per-chain rollup, e.g. { robinhood:{holdings,valueUsd}, solana:{...} } |
livePerpPnl / hyperliquidPerps | object | live 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.
| Field | Type | Meaning |
|---|---|---|
handle / displayName | string | the followed trader; handle is what every other endpoint takes |
followers / following | number | their own social counts |
trades / swapCount | number | lifetime trade and swap counts |
volumeUsd | number | lifetime traded volume (USD) |
pnl24h | number | 24h PnL (USD) |
verified / private | boolean | FOMO verification, and whether the profile is private |
twitter | string | linked X profile, when they have one |
bio / avatar | string | profile description and picture |
createdAt / accountAgeDays | string / number | FOMO signup time and age in days |
truncated / cap | boolean / number | top level: whether the list was cut (at our 300 bound or FOMO's 200 wall), and what our bound is |
complete | boolean | top level: true only when this is the trader's whole following list |
partial / retryable | boolean | top 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.
| Field | Type | Meaning |
|---|---|---|
handle / displayName | string | the follower; handle is what every other endpoint takes |
followers / following | number | their own social counts |
trades / swapCount | number | lifetime trade and swap counts |
volumeUsd | number | lifetime traded volume (USD) |
pnl24h | number | 24h PnL (USD) |
clan | object | their clan {id,name,icon,role}, or null |
verified / private | boolean | FOMO verification, and whether the profile is private |
twitter | string | linked X profile, when they have one |
bio / avatar | string | profile description and picture |
createdAt / accountAgeDays | string / number | FOMO signup time and age in days |
sourceCapped / note | boolean / string | top level: true plus an explanation whenever you are looking at FOMO's 200 wall |
count / cap | number | top 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.
| Field | Type | Meaning |
|---|---|---|
bestTrades / bestTheses | array | FOMO's picks; the same item shape in both |
tradeId | string | pass to /v2/trades/{tradeId} or its /comments |
token / chain | object / string | what was traded and where |
avgEntryPrice / avgExitPrice | number | average in and out |
realizedPnlUsd / unrealizedPnlUsd | number | closed and open PnL on the position |
thesis / thesisLikes | string / number | what they wrote, and how it landed |
openedAt / closedAt | string | when 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
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.
| Field | Type | Meaning |
|---|---|---|
tradeId | string | FOMO trade id |
trader | string | handle who made the trade |
swaps / transfers | array | the on-chain legs of the trade |
avgEntryPrice / avgExitPrice | number | average entry / exit (USD) |
realizedPnlUsd | number | realized PnL (USD) |
isDev | bool | whether 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.
| Field | Type | Meaning |
|---|---|---|
text | string | the comment itself |
userId | string | who wrote it; resolve with /v2/users/id/{userId} |
likes | number | how many people liked it |
parentId / isReply | string / bool | the comment this replies to; null and false on a top-level thesis |
olderThesisCount / newerThesisCount | number | theses the same trader wrote on this position before and after |
links | array | links embedded in the comment, each {text, link, provider} |
Token holders smart money
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.
$ 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.
| Field | Type | Meaning |
|---|---|---|
handle / wallet | string / object | the dev, and their Solana and EVM addresses |
isDev | bool | FOMO's own dev flag for this holder |
amount / valueUsd | number | tokens held and what they are worth |
costBasisUsd / averageEntryPrice | number | what the position cost them |
realizedPnlUsd / unrealizedPnlUsd | number | taken off the table, and still on it |
thesis | string | the 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.
| Field | Type | Meaning |
|---|---|---|
holders | number | total holder count |
top10HoldersPercent | number | share of supply in the top ten wallets |
windows | object | {5m, 1h, 4h, 24h}, each the block below |
buys / sells | number | trade counts in the window |
uniqueBuyers / uniqueSellers | number | distinct wallets on each side |
buyVolumeUsd / sellVolumeUsd | number | dollar volume each way |
netVolumeUsd | number | buy minus sell; positive means bought into |
buySellRatio | number | buys 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.
| Field | Type | Meaning |
|---|---|---|
side | string | buy or sell |
uniqueTraders / trades | number | how many distinct people, and how many trades |
windowMinutes | number | the window they did it in |
volumeUsd / priceChangePercent | number | size of the move and what the price did |
traders | array | the notable participants, each {handle, displayName, userId, avatar} |
topTradersInvolved | bool | FOMO's own flag for "these were top traders, not random wallets" |
stale / newestEventAgeHours | bool / number | top 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}.
Search
Find a trader or a token by name when you do not already have a handle or a contract address.
GET/v2/search?q={query}
Unified search over both traders and tokens in one call. A trader match (by handle or display name) returns both wallets, PnL, and stats. A token match (by symbol or name) returns its mint address, name, image, and market cap. Every result carries a type field (trader or token) so you can tell them apart, and every trader result carries userId, so a hit can be used against the /v2/users/{...}/ sub-resources directly. Note the query matches handle and display name only - searching by userId is not supported; use /v2/users/id/{userId} for that. Use ?type=traders or ?type=tokens to restrict (default all), and ?limit=N to cap results.
curl "https://api.fomoapi.io/v2/search?q=ansem"
curl "https://api.fomoapi.io/v2/search?q=ether&type=traders"
GET/v2/tokens/search?q={query}
Token-only search by symbol or name (for example PONS or ANSEM). Returns {symbol, address, name, image, marketCapUsd} per match. Optional ?limit=N. Use this when you only want tokens and not the trader results that /v2/search mixes in.
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).
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:
?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"?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.| Field | Type | Meaning |
|---|---|---|
text | string | the written thesis |
id / tradeId | string | null | stable thesis dedupe key / related position id for joins |
handle / name | string | trader handle / display name |
userId | string | trader id |
avatar | string | null | author profile-picture URL when FOMO or our resident directory has one; no user lookup is made per card |
tokenImage | string | null | token-image URL when supplied or already cached; no token lookup is made per card |
equity | number | the trader's position value (USD) |
tradeUsd | number | size of the trade (USD) |
isDev | bool | whether the author is the token dev |
likes | number | likes on the thesis (what ?sort=likes ranks by) |
replies | number | replies on the thesis |
ts | string | timestamp |
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
?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/alerts | On-chain /ws/trades | |
|---|---|---|
| What it is | FOMO's live feed, exactly as people see it in the app | Every FOMO trade read straight off the chain |
| When you get it | The same moment the app shows it | About 3.5 seconds before the app shows it |
| What it carries | Buys, sells and theses, plus mobile push alerts (price moves, listings, milestones) | Trades only, with the real fill size, transaction hash and block |
| Chains | Robinhood Chain, Solana, Base, BSC, Ethereum, Monad, Arc, plus Hyperliquid perps | Robinhood Chain, Solana |
| Plans | Every plan (realtime on paid plans, 7-day trial on free) | Growth and Scale |
| Reach for it when | You want to react to what the crowd is seeing, or you need theses and push alerts | Being 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.
| Plan | Credits / month | Billing | Realtime WebSocket | On-chain + OHLCV |
|---|---|---|---|---|
| Free | 250,000 | no card | 7 days realtime, then 15s delayed | - |
| Starter - $49.99/mo | 2,500,000 | card, monthly | realtime | - |
| Builder - $199.99/mo | 12,500,000 | card, monthly | realtime | - |
| Growth - $599/mo | 37,500,000 | card, monthly | realtime | Yes |
| Scale - $1,500/mo | 112,500,000 | card, monthly | realtime · multi-stream | Yes |
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.