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

# Bundle Stream

> Live WebSocket stream of coordinated KOL bundles on a Solana token, the side wallets buying alongside a KOL, with proof and live positions.

The live counterpart to the Bundle Snapshot endpoint. Subscribe to a token and receive its bundles as they are detected and as the wallets trade, a KOL plus the side wallets buying alongside it in the same block. Each wallet carries a full live position, and side wallets carry the on-chain proof fields. This stream is Solana only.

On subscribe to a concrete token you get an init snapshot with the current bundles, then kol\_bundle events as the detector and the market cap update.

## Endpoint

Connect to `wss://stream.cabalspy.xyz`.

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

  ws.onopen = () => {
    ws.send(JSON.stringify({
      op: "subscribe", stream: "bundle",
      token: "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump", mode: "full",
    }));
  };

  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.event === "init" || msg.event === "kol_bundle") {
      msg.data.bundles.forEach(b => console.log(b.kol_wallet, "wallets", b.wallet_count));
    }
  };
  ```

  ```python Python theme={null}
  import json, websocket

  def on_open(ws):
      ws.send(json.dumps({
          "op": "subscribe", "stream": "bundle",
          "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump", "mode": "full",
      }))

  def on_message(ws, message):
      msg = json.loads(message)
      if msg.get("event") in ("init", "kol_bundle"):
          for b in msg["data"]["bundles"]:
              print(b["kol_wallet"], b["wallet_count"])

  ws = websocket.WebSocketApp(
      "wss://stream.cabalspy.xyz?apiKey=YOUR_KEY",
      on_open=on_open, on_message=on_message,
  )
  ws.run_forever()
  ```
</CodeGroup>

## Authentication

Provide your API key as a query parameter, wss\://stream.cabalspy.xyz?apiKey=YOUR\_KEY, or as a header, Authorization: Bearer YOUR\_KEY. Invalid keys are closed with code 1008.

## Subscribe

To start receiving data, send a subscribe message after the connection opens. You choose which token to watch.

```json Subscribe to one token theme={null}
{
  "op": "subscribe",
  "stream": "bundle",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
  "mode": "full"
}
```

The token field decides what you watch. Use a token mint address to follow one token, or the value "*" to follow every tracked token. Following one token also gives you an initial snapshot of its current bundles, "*" does not.

<ParamField body="op" type="string" required>
  The operation. Use subscribe to start.
</ParamField>

<ParamField body="stream" type="string" required>
  Set to bundle for this stream.
</ParamField>

<ParamField body="token" type="string" required>
  Which token to watch. Either a token mint address for one token, or "\*" for all tracked tokens. A single mint also returns a starting snapshot of its bundles.
</ParamField>

<ParamField body="mode" default="full" type="string">
  How much you want to receive. See the modes below.
</ParamField>

<ParamField body="mc_interval" type="integer">
  Optional. Only used with mode full. See the modes below.
</ParamField>

### Choosing a mode

The mode decides whether you also receive live price driven updates, not just detection and trade events. This is the main control over how many messages, and how many credits, you use.

| mode   | What you receive                                                                                                                                | Can you set mc\_interval |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| full   | Detection and trade events plus live position updates every time the market cap moves. This is the default.                                     | Yes                      |
| events | Only real detection and trade events. No market cap updates, so unrealized PnL and remaining value do not tick between trades. Cheapest option. | No, it is ignored        |

With mode full you can slow down the market cap updates to save credits. Set mc\_interval to a number of seconds from 1 to 30, and you get at most one price update per token in that window. Leave mc\_interval out to get every update live.

```json Full, but at most one price update every 5 seconds theme={null}
{
  "op": "subscribe",
  "stream": "bundle",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
  "mode": "full",
  "mc_interval": 5
}
```

```json Events only, no price updates at all theme={null}
{
  "op": "subscribe",
  "stream": "bundle",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
  "mode": "events"
}
```

Setting mc\_interval together with mode events does nothing, because events never sends price updates in the first place.

## Unsubscribe and other operations

To stop receiving a token, send the same token with op unsubscribe.

```json Unsubscribe theme={null}
{
  "op": "unsubscribe",
  "stream": "bundle",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump"
}
```

To see what you are currently subscribed to, send op subscriptions. To check the connection is alive, send op ping and you get a pong back.

```json List your subscriptions theme={null}
{ "op": "subscriptions", "stream": "bundle" }
```

## Events

init on subscribe to a concrete token, then kol\_bundle as bundles change.

<ResponseField name="event" type="string">
  init or kol\_bundle.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="blockchain" type="string">
      solana.
    </ResponseField>

    <ResponseField name="token" type="object">
      Token block with mint, symbol, name, supply, market\_cap, market\_cap\_usd, market\_cap\_currency, price, price\_usd, sol\_price\_usd, pool, on\_curve and bonding\_curve\_progress.
    </ResponseField>

    <ResponseField name="bundles" type="array">
      <Expandable title="bundle">
        <ResponseField name="bundle_id" type="string">
          Bundle identifier, or null.
        </ResponseField>

        <ResponseField name="kol_wallet" type="string">
          The KOL at the center of the bundle.
        </ResponseField>

        <ResponseField name="kol_profile" type="object">
          Profile of the KOL wallet.
        </ResponseField>

        <ResponseField name="confidence" type="string">
          Detection confidence, low, medium, high or verified.
        </ResponseField>

        <ResponseField name="jito_confirmed" type="boolean">
          Whether confirmed as a Jito bundle.
        </ResponseField>

        <ResponseField name="proof_type" type="string">
          The kind of proof, for example jito\_bundle, same\_transaction or on\_chain\_footprint.
        </ResponseField>

        <ResponseField name="slot" type="integer">
          Solana slot, or null.
        </ResponseField>

        <ResponseField name="wallet_count" type="integer">
          Number of wallets in the bundle.
        </ResponseField>

        <ResponseField name="proof" type="string">
          On-chain proof reference.
        </ResponseField>

        <ResponseField name="verify_hint" type="string">
          Hint for verifying the bundle on-chain.
        </ResponseField>

        <ResponseField name="detected_at" type="string">
          When the bundle was detected.
        </ResponseField>

        <ResponseField name="bundle_wallets" type="array">
          <Expandable title="bundle wallet">
            <ResponseField name="address" type="string">
              Wallet address.
            </ResponseField>

            <ResponseField name="is_kol" type="boolean">
              True for the KOL, false for side wallets.
            </ResponseField>

            <ResponseField name="transaction_type" type="string">
              Last transaction type, buy or sell.
            </ResponseField>

            <ResponseField name="signature" type="string">
              Last transaction signature.
            </ResponseField>

            <ResponseField name="entry_source" type="string">
              How the entry was detected.
            </ResponseField>

            <ResponseField name="position_source" type="string">
              Source of the position data, live.
            </ResponseField>

            <ResponseField name="profile" type="object">
              Wallet profile.
            </ResponseField>

            <ResponseField name="fee_lamports" type="integer">
              Side wallets only. Fee paid in lamports.
            </ResponseField>

            <ResponseField name="block_index" type="integer">
              Side wallets only. Index within the block.
            </ResponseField>

            <ResponseField name="adjacent_to_kol" type="boolean">
              Side wallets only. Whether it traded directly next to the KOL.
            </ResponseField>

            <ResponseField name="same_fee" type="boolean">
              Side wallets only. Whether it paid the same fee as the KOL.
            </ResponseField>

            <ResponseField name="occurrences" type="integer">
              Side wallets only. How often this wallet bundled with the KOL.
            </ResponseField>

            <ResponseField name="position" type="object">
              Live position, same fields as the Bundle Snapshot endpoint (held, peak, bought\_tokens, sold\_tokens, bag\_pct, supply\_pct, invested, invested\_usd, max\_single\_buy, buy\_txn, sell\_txn, sold\_value, sold\_value\_usd, realized\_pnl\_sol, realized\_pnl\_usd, entry\_market\_cap, entry\_market\_cap\_usd, unrealized\_pnl\_sol, unrealized\_pnl\_usd, unrealized\_pnl\_pct, remaining\_sol, remaining\_usd, first\_buy\_at, last\_activity\_at).
            </ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json kol_bundle theme={null}
  {
    "success": true,
    "channel": "bundle.solana",
    "event": "kol_bundle",
    "data": {
      "blockchain": "solana",
      "token": {
        "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
        "symbol": "invisibull",
        "name": "invisibull",
        "supply": 1000000000,
        "market_cap": 40.08,
        "market_cap_usd": 3301.2,
        "market_cap_currency": "SOL",
        "price": 4.008e-8,
        "price_usd": 3.3e-6,
        "sol_price_usd": 82.36,
        "pool": "pump",
        "on_curve": true,
        "bonding_curve_progress": 42.5
      },
      "bundles": [
        {
          "bundle_id": "bnd_7f3a2c",
          "kol_wallet": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
          "kol_profile": {
            "name": "Maze",
            "image_url": "https://cabalspy.xyz/images/7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW.png",
            "twitter": "https://x.com/MazeCCC",
            "telegram": "",
            "blockchain": "solana",
            "currency": "SOL",
            "type": "kol"
          },
          "confidence": "verified",
          "jito_confirmed": true,
          "proof_type": "jito_bundle",
          "slot": 301948220,
          "wallet_count": 2,
          "proof": "slot:301948220",
          "verify_hint": "check same slot buys on solscan",
          "detected_at": "2026-07-04T16:25:34Z",
          "bundle_wallets": [
            {
              "address": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
              "is_kol": true,
              "transaction_type": "buy",
              "signature": "5xr8abc...",
              "entry_source": "at_bundle",
              "position_source": "live",
              "profile": {
                "name": "Maze",
                "image_url": "https://cabalspy.xyz/images/7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW.png",
                "twitter": "https://x.com/MazeCCC",
                "telegram": "",
                "blockchain": "solana",
                "currency": "SOL",
                "type": "kol"
              },
              "position": {
                "held": 22113972.03,
                "peak": 22113972.03,
                "bought_tokens": 22113972.03,
                "sold_tokens": 0.0,
                "bag_pct": 100.0,
                "supply_pct": 2.21,
                "invested": 1.66,
                "invested_usd": 137.44,
                "max_single_buy": 1.66,
                "buy_txn": 1,
                "sell_txn": 0,
                "sold_value": 0.0,
                "sold_value_usd": 0.0,
                "realized_pnl_sol": 0.0,
                "realized_pnl_usd": 0.0,
                "entry_market_cap": 44.6,
                "entry_market_cap_usd": 3673.2,
                "unrealized_pnl_sol": -0.099817,
                "unrealized_pnl_usd": -8.22,
                "unrealized_pnl_pct": -10.12,
                "remaining_sol": 0.886537,
                "remaining_usd": 73.02,
                "first_buy_at": "2026-07-04T16:25:34Z",
                "last_activity_at": "2026-07-04T16:40:10Z"
              }
            },
            {
              "address": "SideWa11etAbc123...",
              "is_kol": false,
              "transaction_type": "buy",
              "signature": "9kp2def...",
              "entry_source": "at_bundle",
              "position_source": "live",
              "profile": null,
              "fee_lamports": 5000,
              "block_index": 4,
              "adjacent_to_kol": true,
              "same_fee": true,
              "occurrences": 7,
              "position": {
                "held": 8000000.0,
                "peak": 8000000.0,
                "bought_tokens": 8000000.0,
                "sold_tokens": 0.0,
                "bag_pct": 100.0,
                "supply_pct": 0.8,
                "invested": 0.6,
                "invested_usd": 49.4,
                "max_single_buy": 0.6,
                "buy_txn": 1,
                "sell_txn": 0,
                "sold_value": 0.0,
                "sold_value_usd": 0.0,
                "realized_pnl_sol": 0.0,
                "realized_pnl_usd": 0.0,
                "entry_market_cap": 44.6,
                "entry_market_cap_usd": 3673.2,
                "unrealized_pnl_sol": -0.036,
                "unrealized_pnl_usd": -2.97,
                "unrealized_pnl_pct": -10.12,
                "remaining_sol": 0.32,
                "remaining_usd": 26.4,
                "first_buy_at": "2026-07-04T16:25:34Z",
                "last_activity_at": "2026-07-04T16:25:34Z"
              }
            }
          ]
        }
      ]
    },
    "meta": {
      "request_id": "req_5162c7cf7f87",
      "version": "2.0.0",
      "timestamp": "2026-07-06T11:01:57Z"
    }
  }
  ```
</ResponseExample>

## Billing

Each delivered event counts against your plan. In mode full a busy token can produce frequent updates as the market cap moves, use mc\_interval to throttle them or mode events to receive only detection and trade events.

## Heartbeat

The server sends WebSocket pings and expects pong, which standard clients answer automatically. You can also send op ping at any time.
