Docs

GetOrderBookSnapshot - Get Order Book Data

Retrieve a full order book snapshot with individual order visibility from Hyperliquid L1 Gateway via gRPC. Premium endpoint available on dedicated nodes.

Retrieve a full order book snapshot at a specific point in time from the Hyperliquid L1 Gateway. Returns every individual open order across all markets with complete metadata including order IDs, timestamps, trigger conditions, and child orders.

Dedicated Node Required

This is a premium endpoint available only with dedicated nodes. It is not available on shared infrastructure. Contact support to provision a dedicated node.

Full Code Examples

Clone our gRPC Code Examples Repository for complete, runnable implementations in Go, Python, and Node.js.

When to Use This Method

GetOrderBookSnapshot is essential for:

  • Market Microstructure Analysis - Access every individual order with full metadata
  • Queue Position Tracking - See exact order placement timestamps and sizes
  • Whale Watching - Monitor large orders with their original sizes and trigger conditions
  • Trading Strategies - Analyze order distribution, TP/SL clustering, and trigger order density
  • Risk Management - Assess market impact with individual order granularity

Method Signature

protobuf
rpc GetOrderBookSnapshot(Timestamp) returns (OrderBookSnapshot) {}

Request Message

protobuf
message Timestamp {
  int64 timestamp_ms = 1;
}

Set timestamp_ms to 0 to retrieve the latest snapshot.

Response Message

protobuf
message OrderBookSnapshot {
  // JSON-encoded object containing the full order book
  bytes data = 1;
}

The data field contains a JSON-encoded order book snapshot. This response is large (typically ~129 MB) as it contains every individual open order across all markets.

Large Response Size

Configure your gRPC client with a max receive message length of at least 150 MB (recommended 500 MB) to handle the full response.

Top-Level Structure

JSON
{
  "block": "908730000",
  "timestamp": 1772276651839,
  "data": [ ... ]
}
FieldTypeDescription
blockstringBlock height at which this snapshot was taken
timestampintegerUnix timestamp in milliseconds when the snapshot was taken
dataarrayArray of market entries (one per trading pair). ~656 markets in a typical snapshot

Market Entry Structure

Each element in data is a 2-element array (tuple):

JSON
[
  "BTC",
  [
    [ ...bid orders... ],
    [ ...ask orders... ]
  ]
]
IndexTypeDescription
[0]stringCoin/asset symbol. Perp markets use ticker names (e.g. "BTC", "ETH"). Spot markets use @-prefixed numeric IDs (e.g. "@1", "@105"). Pre-market stocks use xyz: prefix (e.g. "xyz:TSLA")
[1]array[2]Two sub-arrays: [0] = bid orders (sorted descending by price), [1] = ask orders (sorted ascending by price)

Order Object

Each order in the bid/ask arrays is an individual order with full metadata:

JSON
{
  "coin": "BTC",
  "side": "B",
  "limitPx": "84500.0",
  "sz": "0.5",
  "oid": 333003526755,
  "timestamp": 1772276628506,
  "triggerCondition": "N/A",
  "isTrigger": false,
  "triggerPx": "0.0",
  "children": [],
  "isPositionTpsl": false,
  "reduceOnly": false,
  "orderType": "Limit",
  "origSz": "0.5",
  "tif": "Alo",
  "cloid": "0x00000000000000000000000000000318"
}

Field Reference

FieldTypePossible ValuesDescription
coinstringTrading pair symbol (matches the market entry key)
sidestring"B", "A""B" = Buy/Bid, "A" = Ask/Sell
limitPxstringLimit price for this order (decimal as string)
szstringCurrent remaining size (decimal as string)
oidintegerUnique order ID
timestampintegerUnix timestamp in milliseconds when order was placed
triggerConditionstring"N/A", "Triggered", "Price above X", "Price below X"Trigger condition description. "N/A" for regular limit orders
isTriggerbooleantrue if this is a conditional/trigger order (stop-loss, take-profit) that hasn't been activated yet
triggerPxstringTrigger price (decimal as string). "0.0" for non-trigger orders
childrenarrayAttached child orders (e.g. TP/SL attached to a parent). Same structure as the parent. Max 2 children, never nested
isPositionTpslbooleantrue if this is a position-level take-profit/stop-loss order
reduceOnlybooleantrue if this order can only reduce an existing position
orderTypestring"Limit", "Stop Market", "Stop Limit", "Take Profit Market", "Take Profit Limit"The type of order
origSzstringOriginal size when the order was placed (decimal as string)
tifstring or null"Gtc", "Alo", nullTime-in-force. "Gtc" = Good-til-cancelled. "Alo" = Add liquidity only (post-only). null for trigger orders
cloidstring or nullClient order ID (hex string) or null if not set

Coin Symbol Conventions

PatternTypeExamples
Plain tickerPerpetual futures"BTC", "ETH", "AAVE", "ARB"
@ + numberSpot markets"@1", "@10", "@105"
xyz: + tickerPre-market stocks"xyz:TSLA", "xyz:TSM", "xyz:SOFTBANK"

Implementation Examples

Go
package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "os"
    "time"

    pb "hyperliquid-grpc-client/api/v2"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/metadata"
)

type Order struct {
    Coin             string  `json:"coin"`
    Side             string  `json:"side"`
    LimitPx          string  `json:"limitPx"`
    Sz               string  `json:"sz"`
    Oid              int64   `json:"oid"`
    Timestamp        int64   `json:"timestamp"`
    TriggerCondition string  `json:"triggerCondition"`
    IsTrigger        bool    `json:"isTrigger"`
    TriggerPx        string  `json:"triggerPx"`
    Children         []Order `json:"children"`
    IsPositionTpsl   bool    `json:"isPositionTpsl"`
    ReduceOnly       bool    `json:"reduceOnly"`
    OrderType        string  `json:"orderType"`
    OrigSz           string  `json:"origSz"`
    Tif              *string `json:"tif"`
    Cloid            *string `json:"cloid"`
}

type Snapshot struct {
    Block     string          `json:"block"`
    Timestamp int64           `json:"timestamp"`
    Data      json.RawMessage `json:"data"`
}

func getOrderBookSnapshot(ctx context.Context, client pb.HyperliquidL1GatewayClient) (*Snapshot, error) {
    ctx, cancel := context.WithTimeout(ctx, 120*time.Second)
    defer cancel()

    // Request current snapshot (timestamp_ms 0 = latest)
    request := &pb.Timestamp{TimestampMs: 0}
    response, err := client.GetOrderBookSnapshot(ctx, request)
    if err != nil {
        return nil, fmt.Errorf("RPC call failed: %v", err)
    }

    fmt.Printf("Response size: %d bytes\n\n", len(response.Data))

    var snapshot Snapshot
    if err := json.Unmarshal(response.Data, &snapshot); err != nil {
        return nil, fmt.Errorf("failed to parse JSON: %v", err)
    }

    return &snapshot, nil
}

func main() {
    endpoint := os.Getenv("HYPERLIQUID_ENDPOINT")
    apiKey := os.Getenv("API_KEY")

    if endpoint == "" {
        log.Fatal("Error: HYPERLIQUID_ENDPOINT environment variable is required.")
    }

    if apiKey == "" {
        log.Fatal("Error: API_KEY environment variable is required.")
    }

    fmt.Println("Hyperliquid Go gRPC Client - Get Order Book Snapshot")
    fmt.Println("====================================================")
    fmt.Printf("Endpoint: %s\n\n", endpoint)

    // Set up TLS connection
    creds := credentials.NewTLS(nil)

    opts := []grpc.DialOption{
        grpc.WithTransportCredentials(creds),
        grpc.WithDefaultCallOptions(
            grpc.MaxCallRecvMsgSize(500 * 1024 * 1024), // 500MB max
        ),
    }

    fmt.Println("Connecting to gRPC server...")
    conn, err := grpc.NewClient(endpoint, opts...)
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    defer conn.Close()

    client := pb.NewHyperliquidL1GatewayClient(conn)
    fmt.Println("Connected successfully!\n")

    fmt.Println("Fetching order book snapshot...\n")

    // Get order book snapshot with API key
    ctx := context.Background()
    ctx = metadata.AppendToOutgoingContext(ctx, "x-api-key", apiKey)

    snapshot, err := getOrderBookSnapshot(ctx, client)
    if err != nil {
        log.Fatalf("Failed to get order book: %v", err)
    }

    fmt.Printf("Block: %s\n", snapshot.Block)

    timestamp := time.UnixMilli(snapshot.Timestamp)
    fmt.Printf("Time: %s\n", timestamp.Format("2006-01-02 15:04:05 UTC"))

    // Parse the market data
    var markets []json.RawMessage
    if err := json.Unmarshal(snapshot.Data, &markets); err != nil {
        log.Fatalf("Failed to parse markets: %v", err)
    }

    fmt.Printf("Total markets: %d\n", len(markets))
}
Python
import grpc
import json
import sys
import os
from datetime import datetime
from dotenv import load_dotenv
import hyperliquid_pb2
import hyperliquid_pb2_grpc

load_dotenv()

def get_order_book_snapshot():
    endpoint = os.getenv('HYPERLIQUID_ENDPOINT')
    api_key = os.getenv('API_KEY')

    if not endpoint:
        print("Error: HYPERLIQUID_ENDPOINT environment variable is required.")
        sys.exit(1)

    if not api_key:
        print("Error: API_KEY environment variable is required.")
        sys.exit(1)

    print('Hyperliquid Python gRPC Client - Get Order Book Snapshot')
    print('========================================================')
    print(f'Endpoint: {endpoint}\n')

    credentials = grpc.ssl_channel_credentials()

    options = [
        ('grpc.max_receive_message_length', 500 * 1024 * 1024),  # 500MB max
    ]

    metadata = [('x-api-key', api_key)]

    print('Connecting to gRPC server...')
    with grpc.secure_channel(endpoint, credentials, options=options) as channel:
        client = hyperliquid_pb2_grpc.HyperliquidL1GatewayStub(channel)
        print('Connected successfully!\n')

        # Request latest snapshot (timestamp_ms 0 = latest)
        request = hyperliquid_pb2.Timestamp(timestamp_ms=0)

        print('Fetching order book snapshot...\n')

        try:
            response = client.GetOrderBookSnapshot(request, metadata=metadata)
            print(f'Response size: {len(response.data)} bytes\n')

            process_snapshot(response.data)

        except grpc.RpcError as e:
            print(f'RPC error: {e}')
        except Exception as e:
            print(f'Error: {e}')

def process_snapshot(data):
    try:
        snapshot = json.loads(data.decode('utf-8'))

        print('ORDER BOOK SNAPSHOT')
        print('===================')
        print(f'Block: {snapshot["block"]}')

        dt = datetime.fromtimestamp(snapshot['timestamp'] / 1000)
        print(f'Time: {dt.strftime("%Y-%m-%d %H:%M:%S UTC")}')

        markets = snapshot['data']
        print(f'Total markets: {len(markets)}')

        # Count orders across all markets
        total_bids = 0
        total_asks = 0
        for market in markets:
            coin = market[0]
            orders = market[1]
            total_bids += len(orders[0])
            total_asks += len(orders[1])

        print(f'Total bid orders: {total_bids:,}')
        print(f'Total ask orders: {total_asks:,}')
        print(f'Total orders: {total_bids + total_asks:,}')

        # Show top 3 markets by order count
        market_counts = []
        for market in markets:
            coin = market[0]
            orders = market[1]
            count = len(orders[0]) + len(orders[1])
            market_counts.append((coin, count))

        market_counts.sort(key=lambda x: -x[1])

        print('\nTop markets by order count:')
        for coin, count in market_counts[:5]:
            print(f'  {coin}: {count:,} orders')

    except json.JSONDecodeError as e:
        print(f'Failed to parse JSON: {e}')
    except Exception as e:
        print(f'Error processing snapshot: {e}')

if __name__ == '__main__':
    get_order_book_snapshot()
JavaScript
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const path = require('path');
require('dotenv').config();

const PROTO_PATH = path.join(__dirname, 'v2.proto');

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true
});

const proto = grpc.loadPackageDefinition(packageDefinition);

async function getOrderBookSnapshot() {
    const endpoint = process.env.HYPERLIQUID_ENDPOINT;
    const apiKey = process.env.API_KEY;

    if (!endpoint) {
        console.error('Error: HYPERLIQUID_ENDPOINT environment variable is required.');
        process.exit(1);
    }

    if (!apiKey) {
        console.error('Error: API_KEY environment variable is required.');
        process.exit(1);
    }

    console.log('Hyperliquid Node.js gRPC Client - Get Order Book Snapshot');
    console.log('=========================================================');
    console.log(`Endpoint: ${endpoint}\n`);

    const metadata = new grpc.Metadata();
    metadata.add('x-api-key', apiKey);

    // 500MB max to handle the large response
    const client = new proto.hyperliquid_l1_gateway.v2.HyperliquidL1Gateway(
        endpoint,
        grpc.credentials.createSsl(),
        {
            'grpc.max_receive_message_length': 500 * 1024 * 1024
        }
    );

    console.log('Fetching order book snapshot...\n');

    // Request latest snapshot (timestamp_ms 0 = latest)
    client.GetOrderBookSnapshot({ timestamp_ms: 0 }, metadata, (error, response) => {
        if (error) {
            console.error('gRPC error:', error.message);
            return;
        }

        try {
            const snapshot = JSON.parse(response.data);
            console.log(`Response size: ${response.data.length} bytes\n`);

            console.log('ORDER BOOK SNAPSHOT');
            console.log('===================');
            console.log(`Block: ${snapshot.block}`);

            const date = new Date(snapshot.timestamp);
            console.log(`Time: ${date.toISOString()}`);
            console.log(`Total markets: ${snapshot.data.length}`);

            // Count orders across all markets
            let totalBids = 0;
            let totalAsks = 0;
            const marketCounts = [];

            for (const market of snapshot.data) {
                const coin = market[0];
                const orders = market[1];
                const bidCount = orders[0].length;
                const askCount = orders[1].length;
                totalBids += bidCount;
                totalAsks += askCount;
                marketCounts.push({ coin, count: bidCount + askCount });
            }

            console.log(`Total bid orders: ${totalBids.toLocaleString()}`);
            console.log(`Total ask orders: ${totalAsks.toLocaleString()}`);
            console.log(`Total orders: ${(totalBids + totalAsks).toLocaleString()}`);

            // Show top markets by order count
            marketCounts.sort((a, b) => b.count - a.count);
            console.log('\nTop markets by order count:');
            for (const { coin, count } of marketCounts.slice(0, 5)) {
                console.log(`  ${coin}: ${count.toLocaleString()} orders`);
            }

        } catch (error) {
            console.error('Failed to parse response:', error.message);
        }
    });
}

getOrderBookSnapshot();

Response Examples

Simple Limit Order (Bid)

JSON
{
  "coin": "0G",
  "side": "B",
  "limitPx": "0.59341",
  "sz": "52.0",
  "oid": 333003526755,
  "timestamp": 1772276628506,
  "triggerCondition": "N/A",
  "isTrigger": false,
  "triggerPx": "0.0",
  "children": [],
  "isPositionTpsl": false,
  "reduceOnly": false,
  "orderType": "Limit",
  "origSz": "52.0",
  "tif": "Alo",
  "cloid": "0x00000000000000000000000000000318"
}

Order with TP/SL Children

A parent limit order can have up to 2 child orders (stop-loss and/or take-profit) attached:

JSON
{
  "coin": "AAVE",
  "side": "B",
  "limitPx": "104.6",
  "sz": "1.94",
  "oid": 332954766819,
  "timestamp": 1772272640873,
  "triggerCondition": "N/A",
  "isTrigger": false,
  "triggerPx": "0.0",
  "children": [
    {
      "coin": "AAVE",
      "side": "A",
      "limitPx": "94.3",
      "sz": "1.94",
      "oid": 332954766820,
      "timestamp": 1772272640873,
      "triggerCondition": "Price below 102.5",
      "isTrigger": true,
      "triggerPx": "102.5",
      "children": [],
      "isPositionTpsl": false,
      "reduceOnly": true,
      "orderType": "Stop Market",
      "origSz": "1.94",
      "tif": null,
      "cloid": null
    },
    {
      "coin": "AAVE",
      "side": "A",
      "limitPx": "102.0",
      "sz": "1.94",
      "oid": 332954766821,
      "timestamp": 1772272640873,
      "triggerCondition": "Price above 110.87",
      "isTrigger": true,
      "triggerPx": "110.87",
      "children": [],
      "isPositionTpsl": false,
      "reduceOnly": true,
      "orderType": "Take Profit Market",
      "origSz": "1.94",
      "tif": null,
      "cloid": null
    }
  ],
  "isPositionTpsl": false,
  "reduceOnly": false,
  "orderType": "Limit",
  "origSz": "1.94",
  "tif": "Gtc",
  "cloid": null
}

Triggered Order

An order where the trigger condition has already been met and the order is now active:

JSON
{
  "coin": "ARB",
  "side": "A",
  "limitPx": "0.1099",
  "sz": "601.7",
  "oid": 331886608601,
  "timestamp": 1772189655929,
  "triggerCondition": "Triggered",
  "isTrigger": false,
  "triggerPx": "0.0",
  "children": [],
  "isPositionTpsl": false,
  "reduceOnly": true,
  "orderType": "Take Profit Limit",
  "origSz": "601.7",
  "tif": "Gtc",
  "cloid": null
}

Important Notes

  • Individual orders, not aggregated levels. Each entry is an individual order with full metadata (order ID, timestamp, trigger info, children, etc.).
  • Bids are sorted by price descending (highest price first). Asks are sorted by price ascending (lowest price first).
  • Children are never nested. Child orders always have an empty children array.
  • Trigger orders (isTrigger: true) have tif: null and meaningful triggerCondition / triggerPx values.
  • Once triggered, the order becomes isTrigger: false, triggerCondition: "Triggered", triggerPx: "0.0", and receives a tif value (typically "Gtc").

Best Practices

  1. Message Size Configuration: Set gRPC max receive message length to at least 500 MB
  2. Timeout: Use a generous timeout (60-120 seconds) for the RPC call due to the large response
  3. Data Validation: Always validate JSON structure before processing
  4. Error Recovery: Implement retry logic with exponential backoff
  5. Resource Management: Close gRPC connections properly to avoid resource leaks
  6. Streaming Alternative: For continuous updates, consider using StreamOrderbookSnapshots instead

Current Limitations

  • Data Retention: Historical order book data is limited to a 24-hour rolling window
  • Rate Limits: Be mindful of request frequency to avoid overwhelming the service
  • Availability: This endpoint requires a dedicated node and is not available on shared infrastructure

Resources

Need help? Contact our support team or check the Hyperliquid gRPC documentation.