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

# Copy trading stream

> wss://api-id.cabalspy.xyz/v1/copy-trading/stream: every new copy instruction of your instance, live, the moment a copied trader trades.

A WebSocket with every new [copy instruction](https://docs.cabalspy.xyz/cabalspy-id/copy-trading.md#the-instruction) of your instance as it is made. It carries the same instructions as the `copy_trade.instruction` [webhook](https://docs.cabalspy.xyz/cabalspy-id/webhooks.md#copy-trading-events), without the round trip to a public URL, so it suits a trading backend that holds a connection open.

It is available on every plan and only carries your own instance's instructions.

## Connecting

Pass your API key as the `api_key` or `apiKey` query parameter, or as an Authorization Bearer header. The connection starts streaming right away, there is nothing to subscribe to.

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

**api\_key** `string` *required*

Your API key. `apiKey` works too, or an Authorization Bearer header.

**instance** `string`

Your Instance ID. Only needed when your account has several instances.

```javascript JavaScript theme={null}
const ws = new WebSocket("wss://api-id.cabalspy.xyz/v1/copy-trading/stream?api_key=YOUR_KEY");

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  switch (msg.event) {
    case "copy_trade.instruction": {
      const i = msg.data;
      console.log(i.external_id ?? i.csid_id, i.action.side, i.action.symbol, i.action.amount_usd ?? i.action.sell_percent + "%");
      queueCopy(i);   // execute, then report with POST /v1/copy-trading/instructions/status
      break;
    }
    case "error":
      console.error(msg.error.code, msg.error.message);
      break;
  }
};
```

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

async def main():
    url = "wss://api-id.cabalspy.xyz/v1/copy-trading/stream?api_key=YOUR_KEY"
    async with websockets.connect(url) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["event"] == "copy_trade.instruction":
                i = msg["data"]
                print(i["id"], i["action"]["side"], i["action"]["mint"], i["expires_at"])
                queue_copy(i)
            elif msg["event"] == "error":
                print("error", msg["error"])

asyncio.run(main())
```

## Messages

Every message is a JSON object with an `event` field.

| Event | When | Fields |
| - | - | - |
| `ready` | Right after connecting. | `data.events`: the events this stream sends. |
| `copy_trade.instruction` | A copied trader traded and an instruction was made for one of your users. | `id`: the instruction id. `data`: the [instruction object](https://docs.cabalspy.xyz/cabalspy-id/copy-trading.md#the-instruction) plus `external_id`. |
| `heartbeat` | After 30 seconds without a message from you. | `ts`: Unix time in seconds. |
| `pong` | After you send `ping`. | None. |
| `error` | The connection is refused. | `error.code` and `error.message`. The server then closes with code 4403. |

```json ready theme={null}
{ "event": "ready", "data": { "events": ["copy_trade.instruction"] } }
```

```json copy_trade.instruction theme={null}
{
  "event": "copy_trade.instruction",
  "id": "9d4b2f6e-1c3a-5e7f-8a9b-0c1d2e3f4a5b",
  "data": {
    "id": "9d4b2f6e-1c3a-5e7f-8a9b-0c1d2e3f4a5b",
    "type": "copy_instruction",
    "status": "pending",
    "created_at": "2026-11-01T18:02:11Z",
    "expires_at": "2026-11-01T18:04:11Z",
    "csid_id": "3f9a1c2e-5b7d-4e8f-a1b2-c3d4e5f60718",
    "external_id": "user_8412",
    "trader": { "csid_id": "7c2e9a14-6b3d-4f81-9e0a-5d4c3b2a1f09", "username": "MazeCCC", "name": "Maze", "image_url": "https://pbs.twimg.com/profile_images/1/maze_normal.jpg", "kol": true, "profile_url": "https://id.cabalspy.xyz/u/MazeCCC" },
    "action": {
      "blockchain": "solana",
      "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
      "symbol": "CPT",
      "name": "Copycat",
      "logo": "https://...",
      "side": "buy",
      "amount_usd": 20.0,
      "amount_native": 0.133333333,
      "currency": "SOL",
      "sell_percent": null,
      "price_usd": 0.15,
      "max_price_usd": 0.1545,
      "min_price_usd": null,
      "slippage_bps": 300
    },
    "source_trade": { "side": "buy", "label": "new", "amount_usd": 150.0, "price_usd": 0.15, "...": "the trader's trade" },
    "settings": { "enabled": true, "mode": "fixed_usd", "amount_usd": 25.0, "percent": 100.0, "max_usd": 20.0, "sides": "both", "slippage_bps": 300, "updated_at": "2026-11-01T17:58:40Z" },
    "links": { "token": "https://app.cabalspy.xyz/token/3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump", "terminal": "https://app.cabalspy.xyz/token/3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump", "profile": "https://id.cabalspy.xyz/u/MazeCCC" }
  }
}
```

```json heartbeat theme={null}
{ "event": "heartbeat", "ts": 1793548975 }
```

```json error theme={null}
{ "event": "error", "error": { "code": "too_many_connections", "message": "At most 5 copy trading stream connections per API key." } }
```

When several of your services hold a connection, each of them receives every instruction. Deduplicate on the instruction `id` before you execute, and if you also receive the webhook, use the same `id` across both.

## Staying connected

Send the text `ping` at any time, the server answers with `pong`. 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.

Instructions made while you were disconnected are not replayed. Reconnect with a short backoff and fetch what is still open with [GET /v1/copy-trading/instructions](https://docs.cabalspy.xyz/cabalspy-id/copy-trading-instructions.md)`?status=pending`.

## Limits and errors

Each API key can hold at most 5 copy trading stream connections at once, counted separately from the other streams.

| Code | Meaning |
| - | - |
| invalid\_api\_key | API key missing, invalid or not enabled for CabalSpy ID. |
| too\_many\_connections | At most 5 copy trading stream connections per API key. |


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