GaiaExGaiaEx
API

Public WebSocket Streams

Public WebSocket streams for real-time order book depth and spot order book data on GaiaEx. No authentication required.

Perp Order Book Stream

WS wss://openapi.gaiaex.com/ws/market/{symbol}

Real-time L2 order book updates for perpetual symbols. No authentication required.

ParameterTypeDescription
{symbol}string (path)Trading symbol — BTC, ETH, xyz:AAPL, vntl:OPENAI, etc.

Message: orderbook

Sent on every order book change. Contains the full L2 snapshot.

{
  "type": "orderbook",
  "coin": "BTC",
  "levels": [
    {
      "px": "84250.00",
      "sz": "1.2340",
      "n": 3
    }
  ],
  "time": 1743508800000
}
FieldTypeDescription
typestringAlways "orderbook"
coinstringSymbol name
levelsarrayPrice levels — px (price), sz (size), n (number of orders)
timeintServer timestamp in ms

Message: heartbeat

{
  "type": "heartbeat",
  "timestamp": 1743508800000
}

Message: stale / recovered

Sent when the upstream data feed disconnects or reconnects:

{ "type": "stale", "reason": "upstream disconnected" }
{ "type": "recovered" }

Error (invalid symbol)

If the symbol is not found, the server sends an error and closes the connection:

{
  "type": "error",
  "code": "SYMBOL_NOT_SUPPORTED",
  "message": "Symbol 'INVALID' not found."
}

Python Example

import asyncio
import websockets
import json

async def stream_orderbook(symbol: str):
    uri = f"wss://openapi.gaiaex.com/ws/market/{symbol}"
    async with websockets.connect(uri) as ws:
        async for message in ws:
            data = json.loads(message)
            if data["type"] == "orderbook":
                top_bid = data["levels"][0] if data["levels"] else None
                print(f"{symbol} top: {top_bid}")
            elif data["type"] == "heartbeat":
                await ws.send("ping")

asyncio.run(stream_orderbook("BTC"))

JavaScript Example

const symbol = 'BTC';
const ws = new WebSocket(`wss://openapi.gaiaex.com/ws/market/${symbol}`);

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'orderbook') {
    console.log(`${data.coin} levels:`, data.levels.length);
  }
};

ws.onclose = () => console.log('Disconnected, reconnecting...');
ws.onerror = (err) => console.error('WS error:', err);

wscat Example

# Connect to BTC perp order book
wscat -c wss://openapi.gaiaex.com/ws/market/BTC

# Connect to RWA Gold order book
wscat -c wss://openapi.gaiaex.com/ws/market/xyz:GOLD

Spot Order Book Stream

WS wss://openapi.gaiaex.com/ws/market/spot/{symbol}

Real-time L2 order book updates for spot tokens. No authentication required.

ParameterTypeDescription
{symbol}string (path)Spot token name — BTC, ETH, PURR, HYPE, etc.

The message format is identical to the perp order book stream above.

Error (invalid symbol)

{
  "type": "error",
  "code": "SPOT_SYMBOL_NOT_SUPPORTED",
  "message": "Spot symbol 'INVALID' not found. Use /spot/symbols/list."
}

Python Example

import asyncio
import websockets
import json

async def stream_spot(symbol: str):
    uri = f"wss://openapi.gaiaex.com/ws/market/spot/{symbol}"
    async with websockets.connect(uri) as ws:
        async for message in ws:
            data = json.loads(message)
            if data["type"] == "orderbook":
                print(f"Spot {symbol}: {len(data['levels'])} levels")

asyncio.run(stream_spot("HYPE"))

JavaScript Example

const ws = new WebSocket('wss://openapi.gaiaex.com/ws/market/spot/HYPE');

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'orderbook') {
    console.log('Spot levels:', data.levels.length);
  }
};

Ping / Pong

To keep a public WebSocket connection alive, send a text message "ping". The server responds with "pong".

→ "ping"
← "pong"

TIP

Most WebSocket libraries handle protocol-level ping/pong frames automatically. The text-based ping/pong above is an additional application-level keepalive mechanism.

Rate Limits

There is no explicit rate limit on public WebSocket connections. However:

  • Each connection consumes server resources. Do not open more connections than necessary.
  • Use one connection per symbol. Do not rapidly open/close connections.
  • If you need multiple symbols, open one connection per symbol and reuse them.

WARNING

Clients that abuse connections (e.g. opening hundreds per second) may be temporarily blocked.

Terminal Walkthrough

Live Test: BTC Perpetual Order Book

# Terminal 1: Connect to BTC
$ wscat -c wss://openapi.gaiaex.com/ws/market/BTC
Connected (press CTRL+C to quit)
< {"type":"orderbook","coin":"BTC","levels":[{"px":"84250.00","sz":"1.234","n":3},{"px":"84249.50","sz":"0.500","n":1}],"time":1712345678000}
< {"type":"orderbook","coin":"BTC","levels":[{"px":"84252.00","sz":"2.100","n":5}],"time":1712345679000}

# Send keepalive
> ping
< pong

# The stream continues with every orderbook change...
# Press CTRL+C to disconnect

Live Test: Spot Order Book

# Terminal 2: Connect to ETH spot
$ wscat -c wss://openapi.gaiaex.com/ws/market/spot/ETH
Connected (press CTRL+C to quit)
< {"type":"orderbook","coin":"ETH","levels":[...],"time":...}

Live Test: RWA Symbol (Apple Stock)

# RWA symbols use the xyz: prefix
$ wscat -c wss://openapi.gaiaex.com/ws/market/xyz:AAPL
Connected (press CTRL+C to quit)
< {"type":"orderbook","coin":"xyz:AAPL","levels":[{"px":"195.50","sz":"10.0","n":2}],"time":...}

Python: Build a Real-Time Spread Monitor

import asyncio
import json
import websockets

async def spread_monitor(symbol: str):
    uri = f"wss://openapi.gaiaex.com/ws/market/{symbol}"
    async with websockets.connect(uri) as ws:
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] != "orderbook" or not msg.get("levels"):
                continue

            levels = msg["levels"]
            # levels is an array of {px, sz, n} objects
            # Negative sz = asks, Positive sz = bids
            bids = [l for l in levels if float(l["sz"]) > 0]
            asks = [l for l in levels if float(l["sz"]) < 0]

            if bids and asks:
                best_bid = max(bids, key=lambda l: float(l["px"]))
                best_ask = min(asks, key=lambda l: float(l["px"]))
                spread = float(best_ask["px"]) - float(best_bid["px"])
                print(f"{symbol} | Bid: {best_bid['px']} | Ask: {best_ask['px']} | Spread: {spread:.2f}")

asyncio.run(spread_monitor("ETH"))

Message Flow Diagram

Client                                Server
  │                                      │
  │──── WSS Connect ────────────────────▶│
  │                                      │
  │◀──── orderbook (full snapshot) ──────│
  │◀──── orderbook (delta) ──────────────│
  │◀──── orderbook (delta) ──────────────│
  │                                      │
  │──── "ping" ─────────────────────────▶│
  │◀──── "pong" ─────────────────────────│
  │                                      │
  │◀──── heartbeat ──────────────────────│  (no changes for a while)
  │◀──── orderbook (delta) ──────────────│
  │                                      │
  │──── Close ──────────────────────────▶│
  │◀──── Close ACK ──────────────────────│