> 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/rest-api/market-data/l4book.md).

# L4PerpBookSnapshot

{% hint style="info" %}
💧 New endpoint - this endpoint is not a part of original Hyperliquid API and is added by us for builder convenience.
{% endhint %}

### Overview

The `l4Book` endpoint returns L4 orderbook snapshots. It includes all resting limit orders on the book with user addresses, but does **not** include trigger orders (stop-loss, take-profit) unless they have been triggered and placed on the book.

**Key details:**

* Refreshed every second
* Perpetual markets by default; outcome markets available via `marketTypes` or explicit `coins`
* Includes user address for each order

## Request

**Endpoint:** `POST /info`

**All markets (perps only — default):**

```json
{
    "type": "l4Book"
}
```

**All markets, perps + outcomes:**

```json
{
    "type": "l4Book",
    "marketTypes": ["perp", "outcome"]
}
```

**Specific coins (any market type):**

```json
{
    "type": "l4Book",
    "coins": ["ETH", "BTC", "#2870"]
}
```

**Parameters:**

| Parameter     | Type      | Required | Description                                                                                                                                                                                                  |
| ------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `coins`       | string\[] | No       | Specific markets to fetch. Omit for all markets.                                                                                                                                                             |
| `marketTypes` | string\[] | No       | All-markets only. Each entry is `"perp"`, `"spot"`, `"outcome"`, or the wildcard `"*"` (alone) for every type the server currently tracks. Omit for `["perp"]` (default). Rejected if combined with `coins`. |

### Response

**Headers:**

* `Content-Type: application/octet-stream`
* `x-payload-format: multi-zstd`
* `x-compression: inner-zstd`

**Format:** Multi-zstd binary format

```
[count:u32][height:u64][timestamp_ms:u64][len:u32][blob]...
```

Each blob is a zstd-compressed msgpack market snapshot:

```json
{
    "coin": "ETH",
    "bids": [
        {
            "oid": 217148811876,
            "user": "0x742d35cc6634c0532925a3b844bc9e7595f7f2e2",
            "px": "3245.50",
            "sz": "1.5"
        }
    ],
    "asks": [
        {
            "oid": 217148811900,
            "user": "0x010461c14e146ac35fe42271bdc1134ee31c703a",
            "px": "3246.00",
            "sz": "2.0"
        }
    ]
}
```

### Example

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

```python
import struct
import aiohttp
import asyncio
import zstandard as zstd
import msgpack
import os

def parse_l4_book_response(data: bytes) -> dict:
    """Parse multi-zstd L4 book response."""
    offset = 0

    # Read header
    count = struct.unpack_from('<I', data, offset)[0]
    offset += 4
    height = struct.unpack_from('<Q', data, offset)[0]
    offset += 8
    timestamp_ms = struct.unpack_from('<Q', data, offset)[0]
    offset += 8

    # Read and decompress each market
    dctx = zstd.ZstdDecompressor()
    markets = []

    for _ in range(count):
        blob_len = struct.unpack_from('<I', data, offset)[0]
        offset += 4
        blob = data[offset:offset + blob_len]
        offset += blob_len

        # max_output_size needed when zstd frame lacks content size
        decompressed = dctx.decompress(blob, max_output_size=blob_len * 20)
        m = msgpack.unpackb(decompressed, raw=False)

        # Wire format: [coin, bids, asks] where order = [user, px, sz, oid]
        bids = [{"oid": o[3], "user": o[0], "px": o[1], "sz": o[2]} for o in m[1]]
        asks = [{"oid": o[3], "user": o[0], "px": o[1], "sz": o[2]} for o in m[2]]
        markets.append({"coin": m[0], "bids": bids, "asks": asks})

    return {
        'height': height,
        'timestamp_ms': timestamp_ms,
        'markets': markets
    }

async def fetch_l4_book(coins=None):
    """Fetch L4 book snapshot from API."""
    api_key = os.environ.get('HYDROMANCER_API_KEY')
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {api_key}'
    }
    request = {'type': 'l4Book'}
    if coins:
        request['coins'] = coins

    async with aiohttp.ClientSession() as session:
        async with session.post(
            'https://api.hydromancer.xyz/info',
            json=request,
            headers=headers
        ) as resp:
            data = await resp.read()
            return parse_l4_book_response(data)

if __name__ == "__main__":
    result = asyncio.run(fetch_l4_book(['ETH', 'BTC']))
    print(f"Height: {result['height']}")
    for market in result['markets']:
        print(f"{market['coin']}: {len(market['bids'])} bids, {len(market['asks'])} asks")
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const axios = require('axios');
const { decompress } = require('@mongodb-js/zstd');
const msgpack = require('msgpack-lite');

async function parseL4BookResponse(buffer) {
    let offset = 0;

    // Read header
    const count = buffer.readUInt32LE(offset);
    offset += 4;
    const height = buffer.readBigUInt64LE(offset);
    offset += 8;
    const timestampMs = buffer.readBigUInt64LE(offset);
    offset += 8;

    // Read and decompress each market
    const markets = [];

    for (let i = 0; i < count; i++) {
        const blobLen = buffer.readUInt32LE(offset);
        offset += 4;
        const blob = buffer.slice(offset, offset + blobLen);
        offset += blobLen;

        const decompressed = await decompress(blob);
        const m = msgpack.decode(decompressed);

        // Wire format: [coin, bids, asks] where order = [user, px, sz, oid]
        const bids = m[1].map(o => ({ oid: o[3], user: o[0], px: o[1], sz: o[2] }));
        const asks = m[2].map(o => ({ oid: o[3], user: o[0], px: o[1], sz: o[2] }));
        markets.push({ coin: m[0], bids, asks });
    }

    return {
        height: Number(height),
        timestampMs: Number(timestampMs),
        markets
    };
}

async function fetchL4Book(coins = null) {
    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.HYDROMANCER_API_KEY}`
    };

    const request = { type: 'l4Book' };
    if (coins) {
        request.coins = coins;
    }

    const response = await axios.post(
        'https://api.hydromancer.xyz/info',
        request,
        { headers, responseType: 'arraybuffer' }
    );

    return parseL4BookResponse(Buffer.from(response.data));
}

// Example usage
(async () => {
    const result = await fetchL4Book(['ETH', 'BTC']);
    console.log(`Height: ${result.height}`);
    for (const market of result.markets) {
        console.log(`${market.coin}: ${market.bids.length} bids, ${market.asks.length} asks`);
    }
})();
```

{% endtab %}
{% endtabs %}

### Rate limits

* 10 requests per 5 minutes

### Common errors

* **403**: Permission denied - check API key
* **404**: No snapshot available yet - retry shortly
* **429**: Rate limit exceeded
