> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/websocket/bbo.md).

# bbo

Stream the best bid and offer (top-of-book) for a single coin. Updated on change (only sent when the best bid or ask changes). This is the most bandwidth-efficient way to track live prices.

For deeper orderbook data, use [`l2Book`](/readme/websocket/l2book.md). For incremental diffs, use [`l2BookDiff`](/readme/websocket/l2bookdiff.md).

### Subscribe

```json
{
    "method": "subscribe",
    "subscription": {
        "type": "bbo",
        "coin": "BTC"
    }
}
```

**Parameters:**

| Parameter     | Type      | Required | Description                                                                                                                     |
| ------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `coin`        | string    | No       | Coin symbol (e.g., "BTC", "ETH"). One subscription per coin. Omit to subscribe to all coins (requires add-on, see below).       |
| `marketTypes` | string\[] | No       | All-coins only. Filters delivery by market type — see [All-coins BBO](#all-coins-bbo-add-on). Rejected if combined with `coin`. |

### All-coins BBO (add-on)

Omit the `coin` field to receive BBO updates for every market in a single subscription:

```json
{
    "method": "subscribe",
    "subscription": {
        "type": "bbo",
        "marketTypes": ["perp", "outcome"]
    }
}
```

This requires the **bboAll** add-on on your API key. Without it the subscription will be rejected.

The optional `marketTypes` filter restricts the firehose to specific market types. Each entry is `"perp"`, `"spot"`, `"outcome"`, or the wildcard `"*"` (alone) for "every type the server currently tracks." Omitting the field defaults to `["perp"]` — outcome and spot markets do **not** appear unless you opt in. The default never grows; new market types must be added to your `marketTypes` array explicitly. Pass `["*"]` to auto-opt-in to future types.

A second all-coins subscribe with a different `marketTypes` value **replaces** the previous filter rather than coexisting with it. Per-coin BBO subscriptions coexist independently with the all-coins subscription.

### Unsubscribe

```json
{
    "method": "unsubscribe",
    "subscription": {
        "type": "bbo",
        "coin": "BTC"
    }
}
```

### Update data format

Each message contains the best bid and best ask for the subscribed coin:

```json
{
    "channel": "bbo",
    "seq": 5,
    "cursor": "0",
    "data": {
        "coin": "BTC",
        "time": 1704067200000,
        "bbo": [
            {"px": "70325", "sz": "12.5", "n": 3},
            {"px": "70326", "sz": "8.3", "n": 2}
        ]
    }
}
```

The `bbo` array always contains exactly 2 elements: `[best_bid, best_ask]`. If a side is empty (no bids or no asks), that element is `null`.

### Field reference

| Field      | Type   | Description                                            |
| ---------- | ------ | ------------------------------------------------------ |
| `coin`     | string | Market symbol                                          |
| `time`     | number | Block timestamp (milliseconds since epoch)             |
| `bbo`      | array  | `[best_bid, best_ask]` — each is an L2 level or `null` |
| `bbo[].px` | string | Price as decimal string                                |
| `bbo[].sz` | string | Total size at this price level                         |
| `bbo[].n`  | number | Number of orders at this price level                   |

### Examples

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const WebSocket = require('ws');

const ws = new WebSocket(`wss://api.hydromancer.xyz/ws?token=${process.env.HYDROMANCER_API_KEY}`);

ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'connected') {
        ws.send(JSON.stringify({
            method: 'subscribe',
            subscription: { type: 'bbo', coin: 'BTC' }
        }));
    } else if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
    } else if (msg.channel === 'bbo') {
        const { coin, time, bbo } = msg.data;
        const [bid, ask] = bbo;
        console.log(`${coin}: bid=${bid?.px} ask=${ask?.px}`);
    }
});
```

{% endtab %}

{% tab title="Python" %}

```python
import websocket
import json
import os

def on_message(ws, message):
    msg = json.loads(message)

    if msg['type'] == 'connected':
        ws.send(json.dumps({
            "method": "subscribe",
            "subscription": {"type": "bbo", "coin": "BTC"}
        }))
    elif msg['type'] == 'ping':
        ws.send(json.dumps({'type': 'pong'}))
    elif msg.get('channel') == 'bbo':
        d = msg['data']
        bid, ask = d['bbo']
        print(f"{d['coin']}: bid={bid['px'] if bid else 'n/a'} ask={ask['px'] if ask else 'n/a'}")

ws = websocket.WebSocketApp(
    f"wss://api.hydromancer.xyz/ws?token={os.environ.get('HYDROMANCER_API_KEY')}",
    on_message=on_message
)
ws.run_forever()
```

{% endtab %}
{% endtabs %}

### Common errors

1. `Rate limit exceeded` - Too many BBO subscriptions for your tier
2. `Permission denied` - All-coins BBO requires the **bboAll** add-on
