> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fomoscan.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Moving from v2

> Each v2 route with its v3 operation and price, and what changed.

v2 (`https://api.fomoscan.sh/v2`) and v3 take the same key and spend from the same balance. Most v3 operations cost what the v2 route they replace costs; the table under Routes and prices lists both.

## At a glance

| | v2 | v3 |
| - | - | - |
| REST | `https://api.fomoscan.sh/v2/…` | `https://api.fomoscan.sh/v3/…` |
| Reference | `https://api.fomoscan.sh/docs`, `https://docs.fomoscan.sh` | `https://api.fomoscan.sh/v3/docs`, `https://api.fomoscan.sh/v3/openapi.json` |
| Stream | `wss://api.fomoscan.sh/v2/ws`, `wss://api.fomoscan.sh/v2/ws/pump` | `wss://stream.fomoscan.sh/v3`, one socket for every platform |
| Key | `fsk_live_…`, `fsk_test_…` | the same key |
| Headers | `Authorization: Bearer <key>` or `X-Api-Key: <key>` | the same; a key in the query string does not count |
| Answers | flat JSON records, USD as numbers, epoch milliseconds | envelopes and documents, money as integer strings, RFC3339 times |

## Keys, plans and credits

* Your v2 key works on v3. A v3 call spends from the monthly credits, top-up and reveal cap a v2 call spends from, so `GET /v2/me` and `GET /v3/me` report one balance.
* Scopes are the v2 scopes: `basic` covers REST and the stream, `http:read` REST only and `live:read` the stream only.
* The stream takes the plans it takes on v2; [WebSocket](https://docs.fomoscan.sh/v3/websocket.md) lists them.
* A key on a free plan (`pilot` or `explore`) without reveals gets 403 `PERMISSION_DENIED` for every answer that takes a reveal. v2 let such keys reveal a fixed sample of handles and wallets; v3 has no sample set.
* A refusal (400, 401, 402, 403, 429, 503) costs nothing. A 404 costs the operation's miss price, as on v2.

## Routes and prices

Credits per call. A reveal is one unit of the key's monthly reveal cap. Each v3 operation lists every answer and its price under Price.

| v2 | v2 credits | v3 | v3 credits |
| - | - | - | - |
| `GET /v2/user/handle/{handle}` | 100 and a reveal when a wallet is returned; 200 not found | `GET /v3/traders/lookup?handle=`, then `GET /v3/traders/{traderId}/wallets` | 100, 200 with no match; then 100, and a reveal when the page lists a wallet |
| `GET /v2/user/handle/{handle}/profile` | 100; 200 not found | `GET /v3/traders/lookup?handle=` | 100; 200 with no match |
| `GET /v2/user/id/{id}`, `GET /v2/user/id/{id}/profile` | 100; 200 not found | not in v3 yet | |
| `GET /v2/user/wallet/{address}`, `GET /v2/pump/user/wallet/{wallet}` | 30,000 and a reveal, 200 not found; the `/v2/pump/` route 10 | `GET /v3/wallets/{family}/{address}` | 30,000 and a reveal; 10 when every owner holds tier `account`; 200 without an owner |
| `POST /v2/user/handle/{handle}/resolve` | 30,000 and a reveal; 200 on a 202 or 404 | `GET /v3/traders/{traderId}/wallets`, stored wallets only | 100, and a reveal when the page lists a wallet; 200 for an unknown trader |
| `GET /v2/user/handle/{handle}/pnl` | 30,000 and a reveal when `ready`; 200 otherwise | `GET /v3/traders/{traderId}/pnl` | 30,000 and a reveal when `ready`; 200 otherwise |
| `POST /v2/user/handles/pnl` | per handle: 30,000 and a reveal when `ready`, 200 otherwise | `POST /v3/traders/pnl` | per id: 30,000 and a reveal when `ready`, 200 otherwise |
| `GET /v2/pump/user/wallet/{wallet}/pnl` | 500 when `ready`; 200 otherwise | `GET /v3/wallets/svm/{address}/pnl` | 500 when `ready`; 200 otherwise |
| `POST /v2/pump/user/wallets/pnl` | per wallet: 500 when `ready`, 200 otherwise | `POST /v3/wallets/pnl` | per wallet: 500 when `ready`, 200 otherwise |
| `GET /v2/user/handle/{handle}/holdings`, `POST /v2/user/handles/holdings` | 2,500; 1,250 per handle; allow-listed keys only | allow-listed keys only; v3 keeps its own list | 2,500; 1,250 per trader |
| `GET /v2/pump/user/wallet/{wallet}/holdings`, `POST /v2/pump/user/wallets/holdings` | 500 per wallet; allow-listed keys only | not in v3 yet | |
| `GET /v2/thesis` | 200 per started 20 rows of `limit` | `GET /v3/posts` | 200 per started 20 rows of `limit` |
| `GET /v2/thesis/token/{tokenAddress}` | 200 per started 20 rows of `limit` | `GET /v3/tokens/{network}/{address}/posts` | 200 per started 20 rows of `limit` |
| `GET /v2/thesis/user/{id}`, `GET /v2/thesis/user/{id}/token/{tokenAddress}` | 200 per started 20 rows of `limit` | `GET /v3/traders/{traderId}/posts`, with `token=tok_…` for one token | 200 per started 20 rows of `limit`; 200 for an unknown trader |
| `GET /v2/pump/thesis` | 10 for a page of 25 | `GET /v3/posts?platform=pump` | 10 for a page of up to 100 |
| `GET /v2/pump/thesis/token/{mint}` | 10 for a page of 25 | `GET /v3/tokens/solana/{address}/posts?platform=pump` | 10 for a page of up to 100 |
| `GET /v2/pump/thesis/wallet/{wallet}` | 10 for a page of 25 | `GET /v3/wallets/{family}/{address}` once, then `GET /v3/traders/{traderId}/posts?platform=pump` | the wallet lookup once, then 10 for a page of up to 100 |
| `GET /v2/token/{address}` | 100; 200 not found | `GET /v3/tokens/{network}/{address}` | 100; 100 not found |
| `GET /v2/leaderboard/traders`, `GET /v2/leaderboard/clans` | 500 | `GET /v3/leaderboards/fomo/traders`, `GET /v3/leaderboards/fomo/clans` | 500; 200 without a capture |
| `GET /v2/leaderboard/tokens/most-held`, `GET /v2/leaderboard/tokens/trending`, `GET /v2/leaderboard/tokens/graduated` | 10 | `GET /v3/leaderboards/fomo/tokens_most_held`, `GET /v3/leaderboards/fomo/tokens_trending`, `GET /v3/leaderboards/fomo/tokens_graduated` | 10 |
| `GET /v2/pump/leaderboard/traders` | 10 | `GET /v3/leaderboards/pump/traders` | 10 |
| `GET /v2/leaderboard/traders-fomoscan` | 500 | `GET /v3/rankings/traders_net_flow_<window>` | 500; 200 without a snapshot |
| `GET /v2/me` | free | `GET /v3/me` | free |
| `GET /v2/ws`, `GET /v2/ws/pump` | 20,000 per minute per socket | `wss://stream.fomoscan.sh/v3` | 20,000 per started minute per connection |
| `POST /v2/user/handle/{handle}/reveal`, `GET /v2/stats/…` | used by the v2 MCP server | not in v3 yet | |
| `GET /feed/trades` | not metered | `GET /v3/trades` | 200 per started 20 rows of `limit`, plus 100 and a reveal per trader the page shows |
| `GET /feed/trader/{handle}` | not metered | `GET /v3/traders/lookup?handle=`, then `GET /v3/traders/{traderId}`, `GET /v3/traders/{traderId}/wallets` and `GET /v3/traders/{traderId}/trades` | each at its own price |
| `GET /get-followers/{handle}`, `GET /get-following/{handle}` | not metered | `GET /v3/traders/{traderId}/followers`, `GET /v3/traders/{traderId}/following` | 200 per started 20 rows of `limit` |
| `GET /get-user/{handle}`, `GET /get-user-by-id/{id}`, `GET /search` | not metered | `GET /v3/traders/lookup?handle=` for a whole handle; nothing for an id or a prefix | 100; 200 with no match |

New in v3, with no v2 route:

* `GET /v3/traders/{traderId}`: 100; 200 for an unknown id.
* `GET /v3/leaderboards`: free.
* `GET /v3/rankings`: free.
* `GET /v3/capabilities`: free.
* `GET /v3/traders/{traderId}/trades`: 200 per started 20 rows of `limit`, plus 100 and a reveal per trader the page shows.
* `GET /v3/wallets/{family}/{address}/trades`: 200 per started 20 rows of `limit`.

## Ids and lookups

* v3 never returns a platform's own id, such as v2's `id` and `authorId`. Every object carries our id, in the forms [Ids](https://docs.fomoscan.sh/v3/concepts/objects.md#ids) lists.
* v3 has no lookup by a v2 `id`. Find each trader once by handle (`GET /v3/traders/lookup`) or by wallet (`GET /v3/wallets/{family}/{address}`), store the `tdr_` id, and call by it from then on:

```bash theme={null}
curl -s -H "Authorization: Bearer $KEY" "https://api.fomoscan.sh/v3/traders/lookup?handle=tradername"
curl -s -H "Authorization: Bearer $KEY" "https://api.fomoscan.sh/v3/traders/tdr_01jbst8pvcfp79y0938nkrkayd/wallets"
```

```json theme={null}
{ "items": [ { "kind": "trader", "id": "tdr_01jbst8pvcfp79y0938nkrkayd", "canonical": { … }, "sources": { … } } ], "hasMore": false, "nextCursor": null }
```

* A handle can match traders on several platforms. The lookup lists current handles first, then handles an account held in the last 30 days; `platform=` narrows it to one platform.
* A trader merged into another answers 200 with the trader that absorbed it, and `canonical.mergedFrom` lists the old id. Store the id you get back.
* Wallet paths name the address family: `/v2/user/wallet/{address}` is `/v3/wallets/svm/{address}` or `/v3/wallets/evm/{address}`. EVM addresses come back lowercase.
* Token paths and ids name the network: `/v3/tokens/{network}/{address}`, `tok_<network>_<address>`, with `network` one of `solana`, `ethereum`, `base`, `bsc`, `arbitrum`, `optimism`, `polygon`, `robinhood`, `hyperevm`, `monad`, `arc`, `xlayer`, `ink` or `hyperliquid`. v2 inferred the chain from the address.

## Answers

* Every object is an envelope (`kind`, `id`, `canonical`, `sources`); [Objects](https://docs.fomoscan.sh/v3/concepts/objects.md) explains it. PnL, boards, rankings and the key are documents that hold envelopes.
* Money: v2 sent USD as JSON numbers (`"netUsd": 1430.12`). v3 sends an `Amount`: `{ "value": "1430120000", "decimals": 6, "currency": "USD" }`. Read `value` as a big integer or decimal and shift it by `decimals`; a float loses digits. Token prices carry 18 decimals.
* Time: v2 sent and took epoch milliseconds (`fomoCreatedAt`, `updatedAt`, `capturedAt`, `?at=`). v3 sends RFC3339 in UTC with milliseconds (`2026-11-02T09:30:00.000Z`) and takes RFC3339 in `at`; epoch milliseconds in `at` answer 400.
* Nulls: v2 used `null` for a missing value. v3 tells four cases apart: no `sources.<platform>` key (no side on that platform), no block (the platform cannot provide it, or we hold no reading), a `null` field (empty when observed), and `{ "status": "unavailable", "retryable": true }` in place of a block (we could not read it for this call).
* Paging: v2 pages took `before=<id of the last item>` and answered `count`, `hasMore`, `nextBefore`. v3 lists answer `{ "items", "hasMore", "nextCursor" }`; send `nextCursor` back as `cursor`. A cursor works only with the parameters that produced it. Post feeds keep `limit` from 1 to 100, 20 by default.
* v2 ignored a query parameter it did not know. v3 answers 400 `INVALID_ARGUMENT`. `HEAD` answers 404.

## Errors

v2: `{ "error": { "code", "message", "retryAfter" } }`. v3: `{ "error": { "type", "code", "message", "requestId" } }`, with `X-Request-Id` on every response and `Retry-After` on 429 and 503.

| v2 | v3 |
| - | - |
| 400 `VALIDATION_ERROR` | `INVALID_ARGUMENT` |
| 401 `UNAUTHORIZED` | `UNAUTHENTICATED` |
| 402 `QUOTA_EXCEEDED`, `REVEAL_LIMIT_REACHED` | the same codes |
| 403 `FORBIDDEN_SCOPE`, `FREE_TIER_DISABLED`, `SAMPLE_SET_ONLY`, `FORBIDDEN` | `PERMISSION_DENIED` |
| 403 `WEBSOCKET_PLAN_REQUIRED` | `PLAN_REQUIRED` |
| 404 `NOT_FOUND` | `NOT_FOUND` |
| 429 `RATE_LIMITED`, `retryAfter` in the body | `RATE_LIMITED`, `Retry-After` header |
| 500 `INTERNAL_ERROR` | `INTERNAL` |
| 503 `OVERLOADED`, `B2B_DISABLED`, `DATA_UNAVAILABLE`; 504 `TIMEOUT` | 503 `OVERLOADED`, `Retry-After` header |
| 202 `{ "pending": true }` from resolve | none |

## Fields

### User record

* `id` (the platform user id): none. Use the trader `id` (`tdr_`).
* `handle`, `name`, `bio`, `profilePicture`: `handle`, `name`, `bio`, `avatarUrl` of the `profile` block in `sources.<platform>`. `canonical.display` repeats the newest of them.
* `twitter` (a URL): `profile.xHandle`, the handle without `@`.
* `followers`, `following`: the `followGraph` block.
* `solanaAddress`, `evmAddress`: `GET /v3/traders/{traderId}/wallets`, wallet envelopes with `canonical.family`, `canonical.address` and the `tier` of each owner.
* Handle suggestions: a missed lookup answers `suggestions`; `match=closest` answers the closest trader. Prefixes match through suggestions; there is no `/search`.
* `walletAvailable`: `canonical.walletAvailable` on `GET /v3/traders/{traderId}` and `GET /v3/traders/lookup`, at no extra price and without a reveal.
* `banner`, `isPumpUser`, `groupBadges`: none.
* Wallet lookup: v2 answered one user record with the wallet on the other chain. `GET /v3/wallets/{family}/{address}` answers the wallet with every owner and a `tier` each (`proven`, `account`, `partial`, `weak`, `unverified`); `GET /v3/traders/{traderId}` reads an owner.

### PnL

* The path takes the `tdr_` id in place of the handle. The batch body takes `traderIds` in place of `handles` and answers `{ "items": [ { "trader", "found", "pnl" } ] }` in request order, in place of `count`, `missing` and `entries`.
* `status` `no-wallet` is `no_wallet`.
* `wallet`, `evmWallet`: `wallets`, a list of `wal_` ids, filled only when `status` is `ready`.
* `handle`, `displayName`, `avatarUrl`, `twitter`, `followers`: none. Read the trader.
* In each window: `netUsd` is `realized`, `volumeUsd` is `volume`, `buyVolumeUsd` and `sellVolumeUsd` are `buyVolume` and `sellVolume`, `trades` is `tradeCount`, `updatedAt` is `asOf`, and `rank` is `{ "position", "of", "ranking" }`. `origin` says where the figures come from: `provider` on `24h`, `7d` and `30d`, `computed` on `all`.
* `unrealized.unrealizedPnlUsd` and `holdingsUsd` are `unrealized.value` and `holdingsValue`.
* Per window, `unrealizedPnlUsd` and `totalPnlUsd` are `unrealized` and `total`; `approximate`, `stale` and `nextUpdateAt` keep their v2 names.
* Wallet PnL (`GET /v3/wallets/{family}/{address}/pnl`) lists the paired wallet of the other family in `wallets`; `boards` replaces `pumpBoard`. `POST /v3/wallets/pnl` takes up to 100 wallets.
* `all` holds the net flow and rank we compute from our trades. v2 mixed cash flow with whole-wallet figures since 1 January 2025.
* `window` (new) narrows `windows` to one key at the same price.

### Posts

Theses, callouts and stream items are one kind, `post`:

* `thesis`: `canonical.body`. `segments`: `canonical.segments`, each `{ "text", "url" }`.
* `tokenAddress` and `tokenNetwork`, callout `coinMint` and `chain`: `canonical.token.id` (`tok_<network>_<address>`). `tokenSymbol`, `coinSymbol`: `canonical.token.symbol`.
* `authorId` and `authorHandle`, callout `wallet` and `userName`: `canonical.author.trader` and `canonical.author.handle`.
* `fomoCreatedAt`, callout `createdAt`: `canonical.postedAt`. `firstSeenAt` (new) is when we first read the post.
* `likeCount`, `likes`: `canonical.likeCount`. `commentCount`, `replyCount`, `viewCount`: the `engagement` block.
* `holdingsUsd`, `tokenAmount`, `authorTradeUsd`, `realizedPnlUsd`, `unrealizedPnlUsd`, `pnl`, `closedAt`, callout `position`: the `position` block (`value`, `quantity`, `invested`, `realizedPnl`, `unrealizedPnl`, `totalPnl`, `closed`, `closedAt`).
* `calledOutAtMcap`, `multiple`, `maxMultiplier`, `mediaUrl`: the `callPerformance` block (`marketCapAtCall`, `multiple`, `maxMultiple`, `mediaUrl`).
* Thesis `priceUsdAtPost` and `marketCapAtPost`: `callPerformance.priceAtCall` and `callPerformance.marketCapAtCall`.
* `thesisHash`: none. Key on `id`.

### Tokens

* `GET /v2/token/{address}`: `GET /v3/tokens/{network}/{address}`. The `market` block per platform carries `price`, `marketCap`, `liquidity`, `volume24h`, `holders` and `priceChange24h` (percent). A token without platform market data is read live, as on v2, into `canonical.market`.

### Boards and rankings

* `/v2/leaderboard/<board>` and `/v2/pump/leaderboard/traders` are `/v3/leaderboards/{platform}/{board}`; `GET /v3/leaderboards` lists every board with its windows.
* `window` stays. `period` `daily`, `weekly`, `monthly` is `window` `24h`, `7d`, `30d`. `at` takes RFC3339 in place of epoch milliseconds.
* `count` is `entryCount`. An entry is `{ "rank", "subject", "measures" }`: `subject` is the trader, token or group envelope in place of `id`, `handle`, `label` and `avatarUrl`. `measures` holds the figures under snake-case keys, money as amounts: `marketCap` is `market_cap`, `numTrades` is `trade_count`, `memberCount` is `members`.
* `/v2/leaderboard/traders-fomoscan?window=&order=` is `/v3/rankings/traders_net_flow_<window>?order=`. Tied traders share a rank, and `order=worst` answers the bottom entries with their own ranks, where v2 numbered them from 1.

### Account

* `GET /v3/me` moves `entitlement` and `usage` into `api`: `monthlyUnits`, `additionalUnits`, `unitsUsed`, `unitsRemaining`, `ratePerMinute`, `monthlyReveals`, `revealsUsed`, `revealsRemaining`, `unmetered`. Stream minutes count in `api.unitsUsed`.

## Limits and refusals

| | v2 | v3 |
| - | - | - |
| Per key | `ratePerMinute`, a fixed minute | the same `ratePerMinute` |
| Per address, before the key | 2,400 per minute | 2,400 per minute |
| Per address and route, per minute | 2,000 by default; 600 on boards; 120 on `/v2/me` and batches; 10 on resolve | 2,000 by default; 120 on `POST /v3/traders/pnl`, `GET /v3/me` and `POST /v3/wallets/pnl`; 600 on `GET /v3/leaderboards/{platform}/{board}` and `GET /v3/rankings/{key}` |
| Headers | `x-ratelimit-*` for the route limit | `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` for the key |
| 429 | `retryAfter` in the body | `Retry-After` header |
| Load | 503 `OVERLOADED` | 503 `OVERLOADED` with `Retry-After: 1` past 30 requests in flight on one API instance, before the key is checked |
| Requests in flight per key | none | 429 `RATE_LIMITED` with `Retry-After: 1` past 15 requests in flight for one key and its child keys on one API instance |

* A 429 or a 503 costs nothing. Retry after the seconds in `Retry-After`.
* A reveal that waits more than 2 seconds on the reveal lock of the key answers 429 with `Retry-After: 1`, as on v2.

## Stream

| | v2 | v3 |
| - | - | - |
| URL | `wss://api.fomoscan.sh/v2/ws` (theses), `wss://api.fomoscan.sh/v2/ws/pump` (callouts) | `wss://stream.fomoscan.sh/v3`, every platform on one socket |
| Key | `Authorization: Bearer` or `X-Api-Key` on the upgrade | the same; a refusal is an HTTP status: 401, 402, 403 `PERMISSION_DENIED` (scope), 403 `PLAN_REQUIRED`, 429 |
| Subscribe | `{ "type": "subscribe", "subscription": { "type": "all" } }` | `{ "type": "subscribe", "topic": "posts" }` |
| Tokens | `"subscription": { "type": "tokens", "tokens": ["<address>"] }`, up to 50 | `"tokens": ["tok_solana_<mint>"]`, up to 50 |
| Platforms | one socket per feed | `"platforms": [ … ]`, every platform when left out |
| Acknowledgement | `replaced`, then `subscription` | one `subscribed` frame that carries `subscription` and `replaced` |
| Items | `thesis` with `data`, `pump_callout` with `callout` | `post` with `cursor` and `item`, the envelope `GET /v3/posts` returns |
| Errors | `error` with a string | `error` with `{ "type", "code", "message", "requestId" }` |
| Replay | none | `"since": "s_…"` replays the last 5 minutes, then `replay_done` |
| Heartbeat | ping every 30 s, close 1011 after 90 s of silence | the same |
| Subscribe rate | no limit | 10 `subscribe` or `unsubscribe` frames per 60 s |
| Price | 20,000 credits per minute, per socket | 20,000 credits per started minute per connection, charged on connect and every 60 s, subscribed or not |

```json theme={null}
{"type":"subscribe","topic":"posts","platforms":["fomo","pump"],"tokens":["tok_solana_<mint>"],"since":"s_…"}
```

* Close codes: 1001 we restart; 1008 no credit for the next minute, or the key lost access; 1009 a frame over 64 KB; 1011 no pong, or billing failed twice in a row; 1013 8 MB of frames wait unread, or frames arrive faster than we handle them.
* One socket carries every platform, so a client of both v2 sockets pays one minute where v2 charged two.
* A v2 frame sent to v3 answers an `INVALID_ARGUMENT` error frame and changes nothing.
* v3 sends each post once, when it first becomes visible to us. `postedAt` gives its age.
* Reconnect with `since` set to the last `cursor` you handled. [WebSocket](https://docs.fomoscan.sh/v3/websocket.md) holds every frame, error code and a reconnect recipe.

## Not in v3 yet

* Lookup by a v2 `id`: `GET /v2/user/id/{id}`, `GET /v2/user/id/{id}/profile`, and author feeds keyed on that id.
* Reads on demand: v2 fetched the theses of a token it had not read during the call. `GET /v3/tokens/{network}/{address}/posts` answers an empty page.
* Author name, author avatar and token image on each post (`authorName`, `authorAvatarUrl`, `tokenImageUrl`, callout `profileImage`). Read the author and the token by id.
* Percentages and cost basis of the author's position: `percentageRealizedPnl`, `percentageUnrealizedPnl`, callout `pnlPercentage`, `avgEntryPriceUsd`, `avgEntryMcapUsd`, `costBasisAmount`, `amountBought`.
* Wallet holdings with cost basis from the buy history: `GET /v2/pump/user/wallet/{wallet}/holdings`, `POST /v2/pump/user/wallets/holdings`.
* Live resolve: `POST /v2/user/handle/{handle}/resolve` and its 202. v3 answers from stored data.
* The stats and open holdings of the `GET /feed/trader/{handle}` card.
* MCP: the v2 MCP server, its `GET /v2/stats/…` reads and `POST /v2/user/handle/{handle}/reveal`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.