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

# Live theses, every token or a token list (WebSocket)

> The live thesis feed. pump.fun callouts have their own socket at
`/v2/ws/pump`, described below. Attach
once, then declare what you want, every token or a list of tokens, and
change it at any time without reconnecting.

**This path is an upgrade, not a request.** A plain `GET` returns `404`;
only a request carrying the WebSocket handshake headers is served.

**The key cannot come from a browser.** Authentication is the same
`authorization: Bearer <key>` header as every other endpoint, and the
browser `WebSocket` constructor cannot set headers. Hold the socket in your
backend and relay to your front end from there.

**The socket is included from Growth ($899).** Growth, Scale and Enterprise
can connect. Keys on legacy plans keep the websocket access they already
have. Self-serve free keys, Starter and Builder are refused during the
handshake with `403 WEBSOCKET_PLAN_REQUIRED`.

A key that lacks the scope for this path is refused during the handshake
with a `403`, before the upgrade, so you read it as an HTTP status rather
than a dropped socket.

### Subscribing

A connection holds **one** subscription. It is one of two shapes:

```json
{ "type": "subscribe", "subscription": { "type": "all" } }
{ "type": "subscribe", "subscription": { "type": "tokens", "tokens": ["So1111…", "0xabc…"] } }
```

A `subscribe` **replaces** whatever you had before — it does not add to it.
To change your watchlist, send the new list in full. To stop receiving:

```json
{ "type": "unsubscribe" }
```

That is the one thing here likely to catch you out, so we say it out loud
on the wire too — see the `replaced` frame below.

You attach unsubscribed and receive nothing until you ask. A `tokens`
subscription may name up to **50** addresses. Past that, use `all`: it is
one subscription and it already carries every token.

### Frames you receive

On attach:

```json
{ "type": "ready", "connectionId": "4f9c…" }
```

After every accepted frame, your current subscription — the whole of it, so
there is nothing to reconcile (`null` once you unsubscribe):

```json
{ "type": "subscription", "subscription": { "type": "tokens", "tokens": ["So1111…"] } }
```

And, when a `subscribe` tore down an earlier subscription, one frame ahead
of it saying what stopped:

```json
{ "type": "replaced", "previous": { "type": "tokens", "tokens": ["EPjF…"] } }
```

The two always arrive in that order, so a client applying frames as they
land never ends up holding the dead subscription. A rejected `subscribe`
produces neither — nothing was torn down.

Then one frame per matching thesis:

```json
{ "type": "thesis", "data": { "id": "…", "tokenAddress": "…", "tokenNetwork": "sol", … } }
```

`data` is the **same object** `GET /v2/thesis/token/{tokenAddress}` returns
in `items` — same field names (including `authorAvatarUrl` and
`tokenImageUrl`), same `id`, same epoch-millisecond timestamps. It is not a
second shape to code against. Read `data.tokenAddress` to route a frame back
to its token.

`authorAvatarUrl` points at our image host, at a path of the form
`/a/<key>.webp`. A new picture gets a new URL. A picture added in the last
few minutes can show a neutral placeholder until it is ready.

### The pump feed

pump.fun callouts are on `wss://api.fomoscan.sh/v2/ws/pump`. Same
handshake, same plan rule, same subscribe messages and the same
per-minute price as this path. The path picks the feed, so the messages
are identical:

```json
{ "type": "subscribe", "subscription": { "type": "all" } }
{ "type": "subscribe", "subscription": { "type": "tokens", "tokens": ["So1111…"] } }
```

To receive theses and callouts at once, open one socket on each path. Each
socket is metered on its own.

A subscription on `/v2/ws/pump` receives one frame per new callout:

```json
{ "type": "pump_callout", "callout": { "id": "…", "wallet": "…", "coinMint": "…", "thesis": "…", "createdAt": … } }
```

`callout` is the **same object** `GET /v2/pump/thesis` returns in `items` —
same field names, same `id`, same epoch-millisecond `createdAt`. Read
`callout.coinMint` to route a frame back to its token.

There is no replay on this feed either. Take a snapshot from
`GET /v2/pump/thesis` and reconcile on `callout.id`, the same `id` that
endpoint returns. A callout is sent once per socket, and only while it is
recent. Anything older is on `GET /v2/pump/thesis`.

A rejected frame is answered, does not close the socket, and does **not**
change what you are subscribed to — so a typo costs you nothing:

```json
{ "type": "error", "error": "too many addresses: 91 named, 50 is the cap; subscribe to \"all\" to receive every token" }
```

### What you have to know before you build on it

- **There is no replay.** You hear what arrives after you subscribe,
  nothing before it. Take a snapshot from the REST endpoint
  (`GET /v2/thesis/token/{tokenAddress}`, or `GET /v2/pump/thesis` on the
  pump socket) and reconcile on `id`, which is stable and is the same id
  the REST endpoint returns. If the socket drops, fetch the newest page
  from the REST endpoint to close the gap, then reattach and resubscribe.
- **Subscriptions do not survive a reconnect.** A new socket starts empty.
- **A thesis is sent once.** It is never re-sent on the same socket.
- **The socket carries recent theses.** Anything outside that window is on
  the REST endpoint instead.
- **Frames are not ordered by `fomoCreatedAt`.** Sort client-side if order
  matters.
- **`null` means the value was not observed.** An absent `likeCount` is not
  a zero.
- **The pong is mandatory.** The server pings on a fixed interval and
  closes with `1011` a socket it has not heard from for three ping
  intervals. A pong or any frame you send counts. Browser and Node `ws` clients
  answer pings automatically; a hand-rolled client must reply itself. A
  socket killed mid-path by a proxy stays open and silent at both ends,
  and the pong is the only thing that detects it.

Before initial billing completes, the server queues at most 32 frames or 64 KiB
of application frames. Exceeding either limit sends an error frame with
`initialization queue limit exceeded` and closes with `1009`. A single frame
larger than 64 KiB also closes with `1009`.

### Close codes

| code | meaning |
|---|---|
| `1001` | the server is going away; reconnect and resubscribe |
| `1008` | the key is out of quota |
| `1009` | a frame exceeds 64 KiB or the initialization queue exceeds its limits |
| `1011` | no pong or frame from you for three ping intervals |
| `1013` | frames are queueing faster than you read them |

### Billing

This path is metered by the minute held open rather than by the call, from
the same credits as your REST calls. A minute starts when the socket opens,
and the meter stops when it closes. See `GET /v2/me` for your remaining
balance.

The price is **per socket, not per token**: one address, fifty, or every
token all read the same on the meter.

**Price:** 20,000 CU per minute held open.



## OpenAPI

````yaml https://api.fomoscan.sh/openapi.json get /v2/ws
openapi: 3.1.0
info:
  title: FomoScan Identity API
  version: 1.1.0
  description: >-
    Resolve a FOMO trader handle to their on-chain wallet addresses, and

    any wallet back to the traders behind it. Every returned address is a

    verified match.


    ## Authentication

    Send your API key as `Authorization: Bearer fsk_live_…` (or the

    `X-Api-Key` header). Enter it under Authentication at the top of this

    page to call any endpoint live.


    ```bash

    curl -s -H "Authorization: Bearer $KEY" \
      "https://api.fomoscan.sh/v2/user/handle/tradername"
    ```


    ## Response format

    A successful response is the record itself, as flat JSON. There is no

    wrapper to unwrap. Timestamps on thesis and PnL records are epoch

    milliseconds.


    ## Billing

    - A lookup that finds nothing is still charged, at the miss price stated on
    the endpoint.

    - `GET /v2/me` is free and reports your remaining units.

    - Errors that are our fault or your client’s (`401`, `403`, `429`, `5xx`)
    are not billed.

    - When the allowance is spent, calls return `402 QUOTA_EXCEEDED` until the
    counter resets.


    ## Free keys

    A free key reveals wallets for the sample set below and nobody else.

    Sample lookups cost no credits. Outside the set, a wallet lookup, or a

    handle or id lookup whose answer carries a wallet, returns

    `403 SAMPLE_SET_ONLY`. Profiles without a wallet and misses answer as

    they do for any key. A self-serve free key cannot open the WebSocket.


    - Handles: `frankdegods`, `unipcs`, `pointfarmcap`, `orangie`, `theveeman`

    - Wallets: `498g1rVnFcnjBjpfw1xyqA1WvgQXUU8RWuELjxkjAayQ`,
    `0x696d1265c8fc4f14797abebfae3c43ebfa9d8e28`,
    `2heJbC32Tpfcb3nbUb5ER61K11FGZVfVGtVnDm6LDogF`,
    `Beqv6dzTcjV2eodo8RRXCiCcnSYrS1vkQKhfqwHXqeit`,
    `83b2LMf12aLec92kur3duRA8VvUkLaXUtML31aCcZNCM`,
    `F5hkYsi8JxjyA2JHN5CA7MbnnhWubkXB2ZQB7Gkaxqs6`


    ## 404 NOT_FOUND

    A lookup that finds nothing answers `404` with the code `NOT_FOUND`. On

    a handle or id lookup it means we hold no user under that handle or id.

    On a wallet lookup it means we hold no verified link from that address

    to a trader. Treat it as "no answer today", cache the negative briefly

    and ask again later. A user we hold without a verified wallet answers

    `200` with both addresses `null`.


    ## Errors

    Every error uses one shape: `{ "error": { "code": "…", "message": "…" } }`.


    | HTTP | Code | Meaning |

    |---|---|---|

    | 400 | `VALIDATION_ERROR` | A malformed handle, address or query parameter.
    |

    | 400 | `BAD_REQUEST` | The request cannot be served as sent. |

    | 401 | `UNAUTHORIZED` | Missing or invalid API key. |

    | 402 | `QUOTA_EXCEEDED` | Out of credits. |

    | 402 | `REVEAL_LIMIT_REACHED` | The key’s reveal limit is reached. |

    | 403 | `FORBIDDEN` | The key may not perform this action. |

    | 403 | `FORBIDDEN_SCOPE` | The key is not provisioned for this endpoint. |

    | 403 | `FREE_TIER_DISABLED` | The key has no allowance. |

    | 403 | `SAMPLE_SET_ONLY` | A free key asked for a wallet outside the sample
    set. |

    | 403 | `WEBSOCKET_PLAN_REQUIRED` | The key’s plan has no WebSocket. It is
    included from Growth ($899), and legacy plans keep it. |

    | 404 | `NOT_FOUND` | No answer for the handle, id or wallet you asked
    about, or no such route. See the section above. |

    | 409 | `CONFLICT` | The request conflicts with one already accepted. |

    | 429 | `RATE_LIMITED` | Per-minute limit exceeded. Retry after `retryAfter`
    seconds. |

    | 500 | `INTERNAL_ERROR` | Our fault. Retry. If it persists, contact support
    with the path and the time. |

    | 503 | `OVERLOADED` | We are shedding load. Retry. |

    | 503 | `B2B_DISABLED` | The API is switched off on this deployment. |

    | 504 | `TIMEOUT` | The answer did not arrive in time. Retry. |


    A `429` also carries `retryAfter`, in seconds.


    ## Rate limits

    Each key has one per-minute request limit across every endpoint,

    readable from `GET /v2/me` as `ratePerMinute`. A per-IP burst ceiling

    sits behind it. Exceeding either returns `429 RATE_LIMITED`.


    ## Pricing

    Paid plans reach every REST endpoint. Plans differ in credits, request rate,

    wallet reveals and the WebSocket. Credits and CU are the same unit.


    | Plan | Free | Starter | Builder | Growth | Scale | Enterprise |

    | --- | --- | --- | --- | --- | --- | --- |

    | Price per month | $0 | $99 | $299 | $899 | $2,499 | Set in the contract |

    | Credits | 25,000 once, for 30 days | 1,000,000 a month | 5,000,000 a month
    | 20,000,000 a month | 75,000,000 a month | Set in the contract |

    | Requests per minute | 60 | 150 | 300 | 600 | 1,200 | Set in the contract |

    | Wallet reveals per month | Sample set only | 150 | 500 | 1,500 | 4,000 |
    No cap |

    | Price per 30,000-credit reveal | $0 on the sample set | $2.97 | $1.79 |
    $1.35 | $1.00 | Set in the contract |

    | WebSocket | No | No | No | Included | Included | Included |


    Keys on legacy plans keep the WebSocket access they already have.


    ### Wallet reveals

    A wallet reveal is an answer that returns a verified wallet. Each reveal
    counts

    against the plan’s wallet reveals for the month. It costs the credits of the

    endpoint that returned it:


    | Reveal endpoint | Credits per reveal |

    | --- | --- |

    | `GET /v2/user/handle/{handle}` | 100 |

    | `GET /v2/user/wallet/{address}` | 30,000 |

    | `GET /v2/user/id/{id}` | 100 |

    | `POST /v2/user/handle/{handle}/resolve` | 30,000 |

    | `GET /v2/user/handle/{handle}/pnl` | 30,000 |

    | `POST /v2/user/handles/pnl` | 30,000 per handle revealed |


    The price per 30,000-credit reveal in the plan table is what 30,000 credits

    cost on the plan. It applies to `GET /v2/user/wallet/{address}`, `POST
    /v2/user/handle/{handle}/resolve`, `GET /v2/user/handle/{handle}/pnl`, `POST
    /v2/user/handles/pnl`.

    A reveal on another endpoint costs that endpoint’s credits.


    ### Credit cost per endpoint


    | Endpoint | Credits |

    | --- | --- |

    | `GET /v2/user/handle/{handle}` | 100 CU on a hit, 200 CU on a 404. |

    | `GET /v2/user/wallet/{address}` | 30,000 CU on a hit, 200 CU on a 404. |

    | `GET /v2/user/id/{id}` | 100 CU on a hit, 200 CU on a 404. |

    | `POST /v2/user/handle/{handle}/resolve` | 30,000 CU on a hit, 200 CU on a
    202 or a 404. A paused lookup (202 with reason unavailable and retryable
    false) is not charged. |

    | `GET /v2/user/handle/{handle}/pnl` | 30,000 CU on a hit, 200 CU on any
    answer that is not ready, and on a 404. |

    | `POST /v2/user/handles/pnl` | 30,000 CU per entry answered ready, 200 CU
    per entry not ready and per handle in `missing`. |

    | `GET /v2/thesis` | 200 CU per 20 requested rows (`limit`, up to 100). An
    empty page costs the same as a full one. |

    | `GET /v2/thesis/token/{tokenAddress}` | 200 CU per 20 requested rows
    (`limit`, up to 100). An empty page costs the same as a full one. |

    | `GET /v2/thesis/user/{id}` | 200 CU per 20 requested rows (`limit`, up to
    100). An empty page costs the same as a full one. |

    | `GET /v2/thesis/user/{id}/token/{tokenAddress}` | 200 CU per 20 requested
    rows (`limit`, up to 100). An empty page costs the same as a full one. |

    | `GET /v2/me` | Free. |

    | `GET /v2/pump/user/wallet/{wallet}` | 10 CU per call. |

    | `GET /v2/pump/thesis` | 10 CU per call. |

    | `GET /v2/pump/thesis/token/{mint}` | 10 CU per call. |

    | `GET /v2/pump/thesis/wallet/{wallet}` | 10 CU per call. |

    | `GET /v2/pump/leaderboard/traders` | 10 CU per call. |

    | `GET /v2/pump/user/wallet/{wallet}/pnl` | 500 CU per call. |

    | `GET /v2/ws` | 20,000 CU per minute held open. |

    | `GET /v2/ws/pump` | 20,000 CU per minute held open. |


    ### Credit packs

    A pack adds credits on top of a plan. Pack credits do not expire, and they
    are

    spent after the plan’s monthly credits run out.


    | Pack | Price |

    | --- | --- |

    | 250,000 credits | $29 |

    | 1,000,000 credits | $115 |

    | 5,000,000 credits | $349 |

    | 20,000,000 credits | $1,049 |

    | 75,000,000 credits | $2,899 |


    ### Payment

    Plans are paid by card, and Apple Pay counts as a card. Credit packs are
    paid

    in crypto only, with no surcharge.


    ## Chains

    Wallet lookups take a Solana address or an EVM address. A verified EVM
    address

    is the same address on every EVM chain.


    Theses carry their token’s chain in `tokenNetwork`. This table shows the
    value

    the API returns for each chain we serve. Some chains arrive as a short name
    and

    some as a numeric chain id. Match on the exact value in the table.


    | Chain | `tokenNetwork` |

    | --- | --- |

    | Solana | `sol` |

    | Base | `base` |

    | BNB Chain | `bsc` |

    | Robinhood Chain | `robinhood` |

    | Ethereum | `eth` |

    | Arc | `5042` |

    | Monad | `143` |


    A thesis on a chain outside this table arrives as a short name or a numeric
    chain id.


    ## Breaking changes

    A breaking change removes or renames an endpoint, a field or an error code,
    or

    changes what a field means. We announce each one at least 3 days before it

    ships. The notice goes out on the changelog, on the Telegram announcements

    channel (https://t.me/scanfomo) and on every other channel we run.


    New fields can appear without notice. Ignore fields you do not recognise.
  contact:
    name: FomoScan
    url: https://fomoscan.sh
    email: fomoscan-support@proton.me
servers:
  - url: https://api.fomoscan.sh
    description: Production
security: []
tags:
  - name: Identities
    description: Resolve handles ↔ wallets.
  - name: Thesis Feed
    description: >-
      Trader "thesis" posts, newest first, by token, by author, or both. Ask
      with no `before` for the newest page. Clients MUST dedupe on `id`.
  - name: WebSocket
    description: >-
      The streaming surface. Everything above is a request you make; this is a
      connection you hold. It delivers the same thesis objects `Thesis Feed`
      returns.
  - name: Pump
    description: >-
      pump.fun lookups: a user by wallet, the global callout feed or one coin's
      or wallet's wall, and the PnL leaderboard. On pump.fun the wallet IS the
      account, so a wallet lookup here is a stored read, not a resolution.
  - name: Account
    description: 'Your key: entitlements and usage.'
paths:
  /v2/ws:
    get:
      tags:
        - WebSocket
      summary: Live theses, every token or a token list (WebSocket)
      description: >-
        The live thesis feed. pump.fun callouts have their own socket at

        `/v2/ws/pump`, described below. Attach

        once, then declare what you want, every token or a list of tokens, and

        change it at any time without reconnecting.


        **This path is an upgrade, not a request.** A plain `GET` returns `404`;

        only a request carrying the WebSocket handshake headers is served.


        **The key cannot come from a browser.** Authentication is the same

        `authorization: Bearer <key>` header as every other endpoint, and the

        browser `WebSocket` constructor cannot set headers. Hold the socket in
        your

        backend and relay to your front end from there.


        **The socket is included from Growth ($899).** Growth, Scale and
        Enterprise

        can connect. Keys on legacy plans keep the websocket access they already

        have. Self-serve free keys, Starter and Builder are refused during the

        handshake with `403 WEBSOCKET_PLAN_REQUIRED`.


        A key that lacks the scope for this path is refused during the handshake

        with a `403`, before the upgrade, so you read it as an HTTP status
        rather

        than a dropped socket.


        ### Subscribing


        A connection holds **one** subscription. It is one of two shapes:


        ```json

        { "type": "subscribe", "subscription": { "type": "all" } }

        { "type": "subscribe", "subscription": { "type": "tokens", "tokens":
        ["So1111…", "0xabc…"] } }

        ```


        A `subscribe` **replaces** whatever you had before — it does not add to
        it.

        To change your watchlist, send the new list in full. To stop receiving:


        ```json

        { "type": "unsubscribe" }

        ```


        That is the one thing here likely to catch you out, so we say it out
        loud

        on the wire too — see the `replaced` frame below.


        You attach unsubscribed and receive nothing until you ask. A `tokens`

        subscription may name up to **50** addresses. Past that, use `all`: it
        is

        one subscription and it already carries every token.


        ### Frames you receive


        On attach:


        ```json

        { "type": "ready", "connectionId": "4f9c…" }

        ```


        After every accepted frame, your current subscription — the whole of it,
        so

        there is nothing to reconcile (`null` once you unsubscribe):


        ```json

        { "type": "subscription", "subscription": { "type": "tokens", "tokens":
        ["So1111…"] } }

        ```


        And, when a `subscribe` tore down an earlier subscription, one frame
        ahead

        of it saying what stopped:


        ```json

        { "type": "replaced", "previous": { "type": "tokens", "tokens":
        ["EPjF…"] } }

        ```


        The two always arrive in that order, so a client applying frames as they

        land never ends up holding the dead subscription. A rejected `subscribe`

        produces neither — nothing was torn down.


        Then one frame per matching thesis:


        ```json

        { "type": "thesis", "data": { "id": "…", "tokenAddress": "…",
        "tokenNetwork": "sol", … } }

        ```


        `data` is the **same object** `GET /v2/thesis/token/{tokenAddress}`
        returns

        in `items` — same field names (including `authorAvatarUrl` and

        `tokenImageUrl`), same `id`, same epoch-millisecond timestamps. It is
        not a

        second shape to code against. Read `data.tokenAddress` to route a frame
        back

        to its token.


        `authorAvatarUrl` points at our image host, at a path of the form

        `/a/<key>.webp`. A new picture gets a new URL. A picture added in the
        last

        few minutes can show a neutral placeholder until it is ready.


        ### The pump feed


        pump.fun callouts are on `wss://api.fomoscan.sh/v2/ws/pump`. Same

        handshake, same plan rule, same subscribe messages and the same

        per-minute price as this path. The path picks the feed, so the messages

        are identical:


        ```json

        { "type": "subscribe", "subscription": { "type": "all" } }

        { "type": "subscribe", "subscription": { "type": "tokens", "tokens":
        ["So1111…"] } }

        ```


        To receive theses and callouts at once, open one socket on each path.
        Each

        socket is metered on its own.


        A subscription on `/v2/ws/pump` receives one frame per new callout:


        ```json

        { "type": "pump_callout", "callout": { "id": "…", "wallet": "…",
        "coinMint": "…", "thesis": "…", "createdAt": … } }

        ```


        `callout` is the **same object** `GET /v2/pump/thesis` returns in
        `items` —

        same field names, same `id`, same epoch-millisecond `createdAt`. Read

        `callout.coinMint` to route a frame back to its token.


        There is no replay on this feed either. Take a snapshot from

        `GET /v2/pump/thesis` and reconcile on `callout.id`, the same `id` that

        endpoint returns. A callout is sent once per socket, and only while it
        is

        recent. Anything older is on `GET /v2/pump/thesis`.


        A rejected frame is answered, does not close the socket, and does
        **not**

        change what you are subscribed to — so a typo costs you nothing:


        ```json

        { "type": "error", "error": "too many addresses: 91 named, 50 is the
        cap; subscribe to \"all\" to receive every token" }

        ```


        ### What you have to know before you build on it


        - **There is no replay.** You hear what arrives after you subscribe,
          nothing before it. Take a snapshot from the REST endpoint
          (`GET /v2/thesis/token/{tokenAddress}`, or `GET /v2/pump/thesis` on the
          pump socket) and reconcile on `id`, which is stable and is the same id
          the REST endpoint returns. If the socket drops, fetch the newest page
          from the REST endpoint to close the gap, then reattach and resubscribe.
        - **Subscriptions do not survive a reconnect.** A new socket starts
        empty.

        - **A thesis is sent once.** It is never re-sent on the same socket.

        - **The socket carries recent theses.** Anything outside that window is
        on
          the REST endpoint instead.
        - **Frames are not ordered by `fomoCreatedAt`.** Sort client-side if
        order
          matters.
        - **`null` means the value was not observed.** An absent `likeCount` is
        not
          a zero.
        - **The pong is mandatory.** The server pings on a fixed interval and
          closes with `1011` a socket it has not heard from for three ping
          intervals. A pong or any frame you send counts. Browser and Node `ws` clients
          answer pings automatically; a hand-rolled client must reply itself. A
          socket killed mid-path by a proxy stays open and silent at both ends,
          and the pong is the only thing that detects it.

        Before initial billing completes, the server queues at most 32 frames or
        64 KiB

        of application frames. Exceeding either limit sends an error frame with

        `initialization queue limit exceeded` and closes with `1009`. A single
        frame

        larger than 64 KiB also closes with `1009`.


        ### Close codes


        | code | meaning |

        |---|---|

        | `1001` | the server is going away; reconnect and resubscribe |

        | `1008` | the key is out of quota |

        | `1009` | a frame exceeds 64 KiB or the initialization queue exceeds
        its limits |

        | `1011` | no pong or frame from you for three ping intervals |

        | `1013` | frames are queueing faster than you read them |


        ### Billing


        This path is metered by the minute held open rather than by the call,
        from

        the same credits as your REST calls. A minute starts when the socket
        opens,

        and the meter stops when it closes. See `GET /v2/me` for your remaining

        balance.


        The price is **per socket, not per token**: one address, fifty, or every

        token all read the same on the meter.


        **Price:** 20,000 CU per minute held open.
      responses:
        '101':
          description: Switching Protocols — the socket is open.
        '401':
          description: Missing or invalid API key.
        '403':
          description: >-
            The key’s plan has no WebSocket (`WEBSOCKET_PLAN_REQUIRED`), or the
            key lacks the scope (`FORBIDDEN_SCOPE`).
        '404':
          description: The request carried no WebSocket upgrade.
      security:
        - b2bApiKey: []
components:
  securitySchemes:
    b2bApiKey:
      type: http
      scheme: bearer
      description: >-
        B2B API key (`fsk_live_…` / `fsk_test_…`), issued by FomoScan. Also
        accepted via `X-Api-Key`.

````

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