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

# Subscriptions

> Per-channel subscription payloads + subscribe / update / unsubscribe / list / ping commands.

All client commands use the [command envelope](/concepts/websocket/overview#command-envelope):

```json theme={null}
{ "id": <client_correlation_id>, "cmd": "<verb>", "params": { ... } }
```

## Subscribe

Each gateway is a separate socket with its own `sid` namespace.
Subscribe per gateway.

### `/ws/user` — private

```json theme={null}
{
  "id": 1,
  "cmd": "subscribe",
  "params": {
    "subscriptions": [
      { "channel": "user_orders" },
      { "channel": "user_fills" },
      { "channel": "vault_positions", "ids": ["0x1234...abcd"] }
    ]
  }
}
```

```json theme={null}
{
  "id": 1,
  "type": "subscribed",
  "accepted": [
    { "sid": 10, "channel": "user_orders" },
    { "sid": 11, "channel": "user_fills" },
    { "sid": 12, "channel": "vault_positions", "ids": ["0x1234...abcd"] }
  ],
  "rejected": []
}
```

### `/ws/market` — public

```json theme={null}
{
  "id": 1,
  "cmd": "subscribe",
  "params": {
    "subscriptions": [
      { "channel": "token_trade_matches",     "ids": ["12345...", "67890..."] },
      { "channel": "token_trade_settlements", "ids": ["12345..."] },
      { "channel": "token_book",              "ids": ["12345..."] },
      { "channel": "token_ohlc",              "ids": ["12345..."] },
      { "channel": "condition_lifecycle",     "ids": ["0xabc..."] },
      { "channel": "system",                  "ids": ["platform_status"] }
    ]
  }
}
```

```json theme={null}
{
  "id": 1,
  "type": "subscribed",
  "accepted": [
    { "sid": 10, "channel": "token_trade_matches",     "ids": ["12345...", "67890..."] },
    { "sid": 11, "channel": "token_trade_settlements", "ids": ["12345..."] },
    { "sid": 12, "channel": "token_book",              "ids": ["12345..."] },
    { "sid": 13, "channel": "token_ohlc",              "ids": ["12345..."] },
    { "sid": 14, "channel": "condition_lifecycle",     "ids": ["0xabc..."] },
    { "sid": 15, "channel": "system",                  "ids": ["platform_status"] }
  ],
  "rejected": []
}
```

`sid`s are connection-local — each gateway numbers from 1
independently, so a `sid: 10` on the user socket and a `sid: 10` on
the market socket point at completely different subscriptions.
Trying to subscribe to the wrong-gateway channel comes back as
`{ "code": "forbidden" }` in `rejected[]`.

### Normalization rules

* All addresses lowercased.
* `tokenId` is decimal-string form (no leading zeroes).
* `conditionId` is lowercased 32-byte hex.
* Duplicate ids removed.

### Rejection codes

A subscription that fails validation comes back under `rejected[]`:

```json theme={null}
{ "channel": "user_orders", "code": "api_key_scope_missing", "message": "api_key_scope_missing: channel user_orders needs portfolio:read" }
```

The `message` field redundantly prefixes the `code` — that's an
artifact of the gateway, not a separate signal. Route by `code`;
treat `message` as the human-readable detail.

| Code                        | When                                                                                                                                                                         |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_params`            | unknown channel, malformed id (bad hex, bad address), or missing required ids                                                                                                |
| `forbidden`                 | wrong gateway (public channel on `/ws/user` or private channel on `/ws/market`); also raised when `vault_positions` `ids` aren't covered by the API key's `associated_vault` |
| `api_key_scope_missing`     | key is missing a scope required by the channel (private channels need `portfolio:read`)                                                                                      |
| `subscription_cap_exceeded` | this connection already holds the maximum subscriptions allowed                                                                                                              |

## Per-channel reference

### Private — own activity (typed)

#### `user_orders` — own order lifecycle

* Gateway: `/ws/user`
* `ids`: not allowed (always scoped to the key's wallet)
* Required scope: `portfolio:read`
* Pushes: `order_placed`, `order_cancelled`

```json theme={null}
{ "channel": "user_orders" }
```

#### `user_fills` — own chain-confirmed fills

* Gateway: `/ws/user`
* `ids`: not allowed (always scoped to the key's wallet)
* Required scope: `portfolio:read`
* Pushes: `user_fill`

```json theme={null}
{ "channel": "user_fills" }
```

#### `vault_positions` — per-vault balance / split / merge / redeem

* Gateway: `/ws/user`
* `ids`: one or more vault addresses (lowercased)
* Required scope: `portfolio:read`
* Pushes: `vault_position_balance_changed`, `vault_position_split`, `vault_position_merged`, `vault_position_redeemed`

```json theme={null}
{ "channel": "vault_positions", "ids": ["0x1234...abcd"] }
```

Each requested vault must be covered by the API key's
`associated_vault` row; vaults outside the grant come back under
`rejected[]` with `forbidden`.

### Public — trades

#### `token_trade_matches` — low-latency matcher tape

* Gateway: `/ws/market`
* `ids`: one or more outcome `tokenId`s (decimal-string)
* Pushes: `trade_matched` (`source: "matcher"`) per off-chain match

```json theme={null}
{ "channel": "token_trade_matches", "ids": ["12345..."] }
```

Fires the moment the matcher prints a trade — fastest tape feed,
arrives several seconds before the chain settlement.

#### `token_trade_settlements` — chain-confirmed settlements

* Gateway: `/ws/market`
* `ids`: one or more outcome `tokenId`s
* Pushes: `trade_settled` (`source: "chain"`) on indexed `OrderFilled`

```json theme={null}
{ "channel": "token_trade_settlements", "ids": ["12345..."] }
```

Use this for accounting-grade trade confirmations. Carries `txHash`,
`blockNumber`, and `orderHash`.

### Public — books

#### `token_book` — public orderbook snapshots + updates

* Gateway: `/ws/market`
* `ids`: one or more outcome `tokenId`s
* Pushes: `book_snapshot` (initial), `book_update` (full top-of-book), `book_delta` (diff against `prevSeq`), `book_snapshot_failed` (snapshot fetch couldn't complete)

```json theme={null}
{ "channel": "token_book", "ids": ["12345..."] }
```

After subscribe the server pushes one `book_snapshot` (depth 100),
then `book_update` events with monotonically increasing `seq`.
`book_delta` is emitted only when the gateway has contiguous local
book state — if `seq` or `prevSeq` is missing on a producer frame the
gateway drops the live `book_update` rather than emit a divergent
delta.

<Warning>
  **Silent gaps look like idle.** When the gateway drops a frame, you
  see no `book_update` for a while — the same way you'd see nothing on
  a quiet market. Don't equate silence with "no change". If you stop
  receiving `book_update` for the channel for \~30 s on a market you'd
  expect to be active, issue
  [`get_book_snapshot`](#snapshot-refresh-get_book_snapshot)
  proactively to confirm state.
</Warning>

See [Reconnect — orderbook resync](/concepts/websocket/reconnect#orderbook-resync)
for the gap-detection flow built around `book_delta.prevSeq`.

#### `token_ohlc` — rolling 5-second candles

* Gateway: `/ws/market`
* `ids`: one or more outcome `tokenId`s
* Pushes: `ohlc_update` (`interval: "s5"`) — fires whenever the matcher prints inside the active 5-second window

```json theme={null}
{ "channel": "token_ohlc", "ids": ["12345..."] }
```

Cheaper bandwidth than the trade tape if you only need chart bars.
Bars are emitted continuously while open and once final with
`isClosed: true`.

### Public — lifecycle

#### `condition_lifecycle` — market-level lifecycle

* Gateway: `/ws/market`
* `ids`: one or more `conditionId`s (lowercased bytes32 hex)
* Pushes: `market_paused`, `market_unpaused`, `market_resolved`, `market_status`

```json theme={null}
{ "channel": "condition_lifecycle", "ids": ["0xabc..."] }
```

Use for pause/unpause, oracle outcome announcement, resolution / payout
state — anything market-wide that's keyed to the condition rather than
to one outcome token.

#### `system` — platform-wide status

* Gateway: `/ws/market`
* `ids`: must be `["platform_status"]`

```json theme={null}
{ "channel": "system", "ids": ["platform_status"] }
```

Use for the platform freeze / maintenance / degraded-mode banner.

## Resource limits

Per-connection caps. Exceeding any of them surfaces as a structured
error rather than a silent drop:

| Limit                                            | Default  | Error code                                                                                        |
| ------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------- |
| Max active subscriptions per connection          | 256      | `subscription_cap_exceeded` (in `rejected[]`)                                                     |
| Max ids per single subscription / `add_ids` call | **100**  | `subscription_too_many_ids` (in `rejected[]`) — message: `"subscription accepts at most 100 ids"` |
| Max bytes per inbound command frame              | 65 536   | server closes with `1009 too big`                                                                 |
| Command rate                                     | \~50 / s | `too_many_commands` (in `error` push)                                                             |
| Outbound buffer per connection                   | 8 MB     | server closes with `1009 outbound_buffer_full` (slow consumers)                                   |

Exact values may differ by environment — defaults above are the dev
configuration.

## Update existing subscription

For id-based channels (`token_trade_matches`, `token_trade_settlements`,
`token_book`, `token_ohlc`, `condition_lifecycle`, `vault_positions`,
`system`) you can change ids on an existing `sid` instead of opening
a new subscription. `user_orders` and `user_fills` don't carry `ids`,
so `update_subscription` returns `invalid_params` for them.

### Add ids

```json theme={null}
{
  "id": 2,
  "cmd": "update_subscription",
  "params": { "sid": 12, "action": "add_ids", "ids": ["789"] }
}
```

### Remove ids

```json theme={null}
{
  "id": 3,
  "cmd": "update_subscription",
  "params": { "sid": 12, "action": "remove_ids", "ids": ["123"] }
}
```

Response (server returns the resulting full id set):

```json theme={null}
{
  "id": 2,
  "type": "ok",
  "sid": 12,
  "channel": "token_trade_matches",
  "ids": ["456", "789"]
}
```

## Snapshot refresh — `get_book_snapshot`

The `get_book_snapshot` command lets a `token_book` subscriber
request a fresh full-depth snapshot without unsubscribing /
resubscribing. Useful when a sequence gap is detected
(`book_delta.prevSeq != lastSeq`).

```json theme={null}
{
  "id": 7,
  "cmd": "get_book_snapshot",
  "params": { "sid": 14 }
}
```

Or by token ids when you'd rather not look up the `sid`:

```json theme={null}
{
  "id": 7,
  "cmd": "get_book_snapshot",
  "params": { "tokenIds": ["12345..."] }
}
```

Server pushes a `book_snapshot` per resolved subscription, or
`book_snapshot_failed` per token if the snapshot fetch failed:

```json theme={null}
{
  "type": "book_snapshot_failed",
  "sid":  14,
  "channel": "token_book",
  "id":   "12345...",
  "data": { "tokenId": "12345...", "reason": "upstream_unavailable", "tsMs": 1776949200000 }
}
```

Without this command, snapshot refresh used to require an
unsubscribe + resubscribe round-trip — `get_book_snapshot` removes
the gap window.

## Unsubscribe

```json theme={null}
{
  "id": 4,
  "cmd": "unsubscribe",
  "params": { "sids": [12, 13] }
}
```

```json theme={null}
{ "id": 4, "type": "unsubscribed", "sids": [12, 13] }
```

## List subscriptions

```json theme={null}
{ "id": 5, "cmd": "list_subscriptions" }
```

```json theme={null}
{
  "id": 5,
  "type": "subscriptions",
  "items": [
    { "sid": 10, "channel": "user_orders" },
    { "sid": 11, "channel": "user_fills" }
  ]
}
```

Useful after a reconnect to confirm what the server thinks you're
subscribed to before rebuilding state.

## Ping

```json theme={null}
{ "id": 6, "cmd": "ping" }
```

```json theme={null}
{ "id": 6, "type": "pong", "ts": 1776949200000 }
```

`ts` is the server clock in milliseconds — clients can use it as a
clock-skew probe.
