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

# Commands

> Client → server commands: subscribe, update_subscription, unsubscribe, list_subscriptions, get_book_snapshot, ping.

Every client → server message uses the same envelope on both gateways:

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

* `id` — client-side correlation id; echoed in the response.
* `cmd` — one of `subscribe` / `update_subscription` / `unsubscribe` /
  `list_subscriptions` / `get_book_snapshot` / `ping`.
* `params` — command-specific payload (omitted for `ping` and
  `list_subscriptions`).

Every accepted subscription gets a connection-local **`sid`** (numeric).
Use `sid` for subsequent updates / unsubscribes and to route incoming
events. `sid`s are **not** stable across reconnects.

## `subscribe`

On `/ws/user`:

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

On `/ws/market`:

```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"] }
    ]
  }
}
```

Response (market gateway, with a bad tokenId rejected):

```json theme={null}
{
  "id": 1,
  "type": "subscribed",
  "accepted": [
    { "sid": 12, "channel": "token_trade_matches",     "ids": ["12345...", "67890..."] },
    { "sid": 13, "channel": "token_trade_settlements", "ids": ["12345..."] },
    { "sid": 14, "channel": "token_book",              "ids": ["12345..."] },
    { "sid": 15, "channel": "token_ohlc",              "ids": ["12345..."] },
    { "sid": 16, "channel": "condition_lifecycle",     "ids": ["0xabc..."] },
    { "sid": 17, "channel": "system",                  "ids": ["platform_status"] }
  ],
  "rejected": [
    {
      "channel": "token_trade_matches",
      "ids":     ["not-a-token-id"],
      "code":    "invalid_params",
      "message": "invalid tokenId"
    }
  ]
}
```

### Normalization rules

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

### Rejection codes

| `code`                      | When                                                                                                                                                                                                  |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_params`            | unknown channel, malformed id, missing required `ids`, or wallet-scoped channel (`user_orders` / `user_fills`) sent with `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` row                      |
| `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 (256); existing subscriptions are not evicted                                                                                                 |
| `subscription_too_many_ids` | a single subscription / `add_ids` call exceeded the per-channel id cap (100). Message: `"subscription accepts at most 100 ids"`. Split your ids across multiple subscriptions on the same connection. |

***

## `update_subscription`

Modify the id list of an **existing** id-based subscription instead of
opening a new one. Not allowed on subscription-less channels
(`user_orders`, `user_fills`).

### 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"]
}
```

`update_subscription` on a subscription-less channel is rejected with
`invalid_params`.

***

## `get_book_snapshot`

Force a fresh full-depth `book_snapshot` on a `token_book`
subscription without unsubscribing / resubscribing. Use on a sequence
gap (`book_delta.prevSeq != lastSeq`) or after a long pause.

By `sid`:

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

By token ids (server resolves to the matching subscription):

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

Response is one `book_snapshot` per resolved subscription, OR one
`book_snapshot_failed` per token if the snapshot fetch couldn't
complete:

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

***

## `unsubscribe`

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

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

Unknown `sid`s are silently ignored (idempotent — the response only
lists `sid`s that were actually removed).

***

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

There is no application-level idle timeout, but production clients
should still send a `ping` every 20–30 s to defeat intermediate
proxies / load balancers that close idle TCP connections.
