# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hydromancer.xyz/readme/rest-api/market-data/l4book.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
