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

# Theses stream

> wss://api-id.cabalspy.xyz/v1/theses/stream: live theses, replies, reactions and removals for the tokens you subscribe to. Every plan.

A dedicated WebSocket for theses. Subscribe to up to 200 tokens, or to all of them, and receive every new thesis, reply, reaction and removal the moment it happens. It is included on **every plan**, Free too.

Use it to keep a thesis list up to date, to show a live feed, or to put theses as markers on a token's price chart.

<Note>
  The general [live stream](/cabalspy-id/stream) can also carry thesis events with `events=theses`, together with your users' trades, but it needs the Pro plan. For theses only, use this stream.
</Note>

## Connecting

```text theme={null}
wss://api-id.cabalspy.xyz/v1/theses/stream?apiKey=YOUR_KEY
```

<ParamField query="apiKey" type="string" required>
  Your API key. `api_key` works too, or an Authorization Bearer header.
</ParamField>

<ParamField query="scope" default="global" type="string">
  `global` for theses of all platforms, `instance` for only the theses written on your instance (see [Reach](/cabalspy-id/theses#reach)).
</ParamField>

<ParamField query="instance" type="string">
  Your Instance ID. Only needed when your account has several instances.
</ParamField>

Right after connecting the server sends `ready`. Then subscribe with JSON frames.

## Frames you send

| Frame | Effect |
| - | - |
| `{"op": "subscribe", "tokens": [{"blockchain": "solana", "mint": "..."}]}` | Adds tokens. Up to 200 per connection in total. |
| `{"op": "unsubscribe", "tokens": [{"blockchain": "solana", "mint": "..."}]}` | Removes tokens. |
| `{"op": "subscribe", "all": true}` | Every token. |
| `{"op": "unsubscribe", "all": true}` | Removes everything. |
| `ping` | The server answers `pong`. |

Every subscribe or unsubscribe is answered with `subscribed`, the complete current subscription.

## Events you receive

| Event | Content |
| - | - |
| `ready` | `data.scope` and `data.max_tokens` (200). |
| `subscribed` | `data.all` and `data.tokens`, your current subscription. |
| `thesis` | A new thesis: `data` is the thesis object as in [List theses](/cabalspy-id/theses-list#response), plus `blockchain`. |
| `thesis.reply` | A new reply, same shape. |
| `thesis.reaction` | `data.id` and `data.reactions`, the new counts, plus `blockchain` and `mint`. |
| `thesis.removed` | `data.id` of a deleted or hidden post, plus `blockchain` and `mint`. |
| `heartbeat` | After 30 seconds without a frame from you, with `ts` (Unix time). |
| `pong` | Answer to `ping`. |
| `error` | `error.code` and `error.message`. |

```json ready theme={null}
{ "event": "ready", "data": { "scope": "global", "max_tokens": 200 } }
```

```json subscribed theme={null}
{ "event": "subscribed", "data": { "all": false, "tokens": [{ "blockchain": "solana", "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump" }] } }
```

```json thesis theme={null}
{
  "event": "thesis",
  "data": {
    "blockchain": "solana",
    "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
    "id": "8d1f5a2e-6c4b-4e0f-9a7d-1b2c3d4e5f60",
    "chain": "solana",
    "kind": "thesis",
    "parent_id": null,
    "body": "Strong community, still early.",
    "stance": "bull",
    "author": { "csid_id": "0b7c4a52-3f1e-4f43-9d0a-2b5e7c1d9a11", "username": "MazeCCC", "name": "Maze", "image_url": "https://pbs.twimg.com/profile_images/.../photo.jpg", "kol_badge": true, "profile_url": "https://id.cabalspy.xyz/u/MazeCCC" },
    "likes": 0,
    "replies": 0,
    "reactions": {},
    "my_reactions": [],
    "via": "Example Terminal",
    "created_at": "2026-10-13T09:12:44.512113+00:00",
    "is_mine": false,
    "position": { "private": true }
  }
}
```

## Example: theses as chart markers

Subscribe to the tokens on screen and drop a marker at each thesis's `created_at`, coloured by `stance`, labelled with the author.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const ws = new WebSocket("wss://api-id.cabalspy.xyz/v1/theses/stream?apiKey=YOUR_KEY");

  ws.onopen = () => ws.send(JSON.stringify({
    op: "subscribe",
    tokens: [{ blockchain: "solana", mint: "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump" }],
  }));

  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.event === "thesis") {
      const t = msg.data;
      chart.addMarker({
        time: Math.floor(new Date(t.created_at).getTime() / 1000),
        color: t.stance === "bear" ? "#ff5c6c" : t.stance === "bull" ? "#3ddc84" : "#8b96a3",
        text: `${t.author.kol_badge ? "KOL " : ""}@${t.author.username}`,
        id: t.id,
      });
    } else if (msg.event === "thesis.removed") {
      chart.removeMarker(msg.data.id);
    } else if (msg.event === "error") {
      console.error(msg.error.code, msg.error.message);
    }
  };
  ```

  ```python Python theme={null}
  import asyncio, json, websockets

  async def main():
      url = "wss://api-id.cabalspy.xyz/v1/theses/stream?apiKey=YOUR_KEY"
      async with websockets.connect(url) as ws:
          await ws.send(json.dumps({"op": "subscribe", "tokens": [
              {"blockchain": "solana", "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump"}]}))
          async for raw in ws:
              msg = json.loads(raw)
              if msg["event"] == "thesis":
                  t = msg["data"]
                  print(t["created_at"], t["stance"], t["author"]["username"], t["body"][:60])

  asyncio.run(main())
  ```
</CodeGroup>

`chart.addMarker` and `chart.removeMarker` stand for your charting library. For markers of theses posted before the page opened, load them with [GET /v1/theses](/cabalspy-id/theses-list).

## Staying connected

When you send nothing for 30 seconds the server sends a `heartbeat` and checks that your key is still valid; a revoked or disabled key ends the connection. Reconnect with a short backoff, send your subscribe frame again, and fill any gap with [GET /v1/theses](/cabalspy-id/theses-list).

## Limits and errors

Each API key can hold at most 5 theses stream connections at once, each with up to 200 tokens.

| Code | Meaning | Connection |
| - | - | - |
| invalid\_api\_key | API key missing, invalid or not enabled for CabalSpy ID. | Closed (4403) |
| invalid\_parameter | scope invalid. | Closed (4403) |
| too\_many\_connections | At most 5 theses stream connections per API key. | Closed (4403) |
| too\_many\_tokens | More than 200 tokens on one connection. The frame is ignored. | Stays open |
| invalid\_frame | The frame is not JSON or not a known op. | Stays open |
| invalid\_parameter | A token in a frame has an unknown chain or an invalid address. | Stays open |


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