# Real-Time Stream

> A WebSocket for agents. WakeTrades pushes every copy event and every move of your leaders as it happens, and takes commands back (pause, close, emergency stop). Open GET /api/v1/stream with your session token.

The stream is a WebSocket between your agent and its copier. WakeTrades pushes every event as it happens: the same events as the [webhook](https://waketrades.com/docs/api/webhooks.md), plus each move of the leaders you follow. Your agent can send commands back on the same connection.

## Open the Stream

`GET wss://waketrades.com/api/v1/stream` with the header `Authorization: Bearer <session token>`.

- It uses the same session token as the rest of the API ([Sign In With a Wallet](https://waketrades.com/docs/api/authentication.md)). When the token expires, the stream closes with code `4001`: sign in again and reopen it.
- You can keep up to 3 streams open at the same time per account. Beyond that, you get `429 too_many_streams`.
- A request without the `Upgrade: websocket` header gets `426 upgrade_required`.
- Browsers can't add an `Authorization` header to a WebSocket: open the stream from a server or an agent runtime.

**Python** (`websockets` library)

```python
import asyncio, json, websockets

async def run(token: str):
    async with websockets.connect("wss://waketrades.com/api/v1/stream",
                                  additional_headers={"Authorization": f"Bearer {token}"}) as ws:
        print(json.loads(await ws.recv()))                      # hello
        await ws.send(json.dumps({"id": "1", "type": "positions"}))
        async for raw in ws:
            msg = json.loads(raw)
            print(msg["type"], msg.get("data"))
```

**JavaScript (Node)** (`ws` library)

```js
import WebSocket from "ws";

const ws = new WebSocket("wss://waketrades.com/api/v1/stream", { headers: { Authorization: `Bearer ${token}` } });
ws.on("open", () => ws.send(JSON.stringify({ id: "1", type: "positions" })));
ws.on("message", (raw) => {
  const msg = JSON.parse(raw);
  console.log(msg.type, msg.data);
});
```

## First Message

```json
{
  "type": "hello",
  "protocol": "waketrades.stream.v1",
  "account": "0xyour_wallet",
  "expiresAt": 1791633600000,
  "commands": ["ping", "status", "positions", "orders", "journal", "pause", "resume", "close_position", "close_all", "emergency_stop"]
}
```

`expiresAt` is when your session token ends, in ms.

## Events

Events have the same shape as webhook events: `{ "id", "type", "created", "account", "data" }`.

- **Every event of your copier**: the types listed in [Webhooks](https://waketrades.com/docs/api/webhooks.md), and also the ones the webhook doesn't push (`skip`, `error`, `state`, `payment`, `limit_placed`…). A `skip` carries a stable `skip` code (`too_small`, `full`, `late`, `not_unified`…), like `GET /orders`. Muting notifications doesn't change the stream.
- **Your leaders' moves**, as WakeTrades sees them, sent before your copy reacts:

| `type` | When |
| --- | --- |
| `leader_open` | A leader you follow opens a position |
| `leader_add`, `leader_cut` | The leader increases or reduces it |
| `leader_close` | The leader closes it |
| `leader_flip` | The leader reverses it (long to short, or short to long) |
| `leader_tpsl` | The leader changes the take profit or stop loss of a position opened since you follow them |

```json
{
  "id": "evt_mg8k2l1c_4be01a7d",
  "type": "leader_open",
  "created": 1791633600000,
  "account": "0xyour_wallet",
  "data": { "leader": "0xleader…", "coin": "ETH", "side": 1, "sz": 4, "prevSz": 0, "entryPx": 2450.5, "leverage": 10, "leverageType": "cross", "mid": 2451 }
}
```

`side` is `1` for long and `-1` for short. `sz` is the leader's size in coins now (`0` after a close), and `prevSz` the size before. `tpsl`, when known, lists the take profits and stops as `[price, share of the position]`.

The stream doesn't keep events while you're disconnected. After a reconnection, catch up with the `journal` command or `GET /api/v1/journal`.

## Commands

Send JSON: `{ "id": "your-id", "type": "<command>" }`. Each command gets one answer with the same `id`:

```json
{ "type": "result", "id": "your-id", "ok": true, "data": { } }
{ "type": "result", "id": "your-id", "ok": false, "error": "alert_channel_required", "message": "…" }
```

| Command | Fields | What it does |
| --- | --- | --- |
| `ping` | | Answers `{ "pong": true }` |
| `status` | | Copying state, exposure level, leaders, open copies, trial and plan dates |
| `positions` | | Copied positions with stop and take profit, like `GET /positions` (read again at most every 30 seconds, or right after an order is sent; longer during a Hyperliquid outage or heavy load) |
| `orders` | `n` (1 to 100, default 20) | Copy orders, including skipped trades and why, like `GET /orders` |
| `journal` | `n` (1 to 100, default 20) | Recent events, newest first |
| `pause` | | Stop opening new copies; open ones stay managed |
| `resume` | | Resume copying (needs Telegram or a webhook) |
| `close_position` | `coin` | Close one copy at market |
| `close_all` | | Close every copy at market and pause |
| `emergency_stop` | | The same as `POST /emergency-stop`: close everything and pause, with what it doesn't do (`keyRevoked: false`) |

Error codes: `bad_request`, `unknown_command`, `alert_channel_required`, `command_failed`, `geo_blocked` (write commands from a [restricted jurisdiction](https://waketrades.com/docs/api/index.md#rules-that-apply-to-agents), checked when the stream opens) and `rate_limited` (more than 60 commands per minute on one stream, or more than 180 per minute on all of an account's streams, reconnections included).

## Close Codes

| Code | Why |
| --- | --- |
| `4001` | The session token expired |
| `4003` | The account was deleted or suspended |
