API Reference
WebSocket

WebSocket

Real-time stream of trade updates, fills, and (optionally) order book data. Lower latency than polling REST and zero per-request rate cost beyond the 10 msg/s receive cap.

Connecting

wss://api.spreadr.xyz/api/v1/ws

Auth option 1 — Header (recommended for bots)

X-API-Key: sprdr_...

The API key must have trades:read scope. With this header set, the connection authenticates as part of the WebSocket upgrade — no first message required.

Auth option 2 — First message

If no auth header is provided, you have 5 seconds to send an auth message before the server closes the connection.

{
  "type": "auth",
  "api_key": "sprdr_..."
}

Or with a Privy JWT (used by the web terminal):

{
  "type": "auth",
  "token": "<privy_jwt>"
}

On success the server replies:

{
  "type": "authenticated",
  "timestamp": "2026-03-12T13:00:00Z"
}

Subscribing

After auth, send subscribe messages to receive events.

All your trades

{
  "type": "subscribe",
  "data": { "subscription": "trades" }
}

You'll receive updates for every trade you own — status transitions, fill events, etc.

One specific trade

{
  "type": "subscribe",
  "data": {
    "subscription": "trades",
    "params": { "trade_id": "00000000-0000-0000-0000-000000000000" }
  }
}

Order book stream

{
  "type": "subscribe",
  "data": {
    "subscription": "orderbooks",
    "params": { "exchange": "hyperliquid", "market": "HYPE" }
  }
}

Fills

{
  "type": "subscribe",
  "data": { "subscription": "fills" }
}

Message types from server

TypeDescription
authenticatedAuth successful (response to your auth message)
tradeTrade status update (e.g. pending → active → completed)
eventTrade event (fill, exchange disconnect, etc.)
fillStandalone fill notification
orderbookOrder book snapshot for an orderbooks subscription
positionPosition update (for a positions subscription)
maintenanceMaintenance mode change
kill_switchKill switch activated or deactivated
risk_alertRisk warning for one of your trades
exchange_grantsYour exchange-access grants changed
errorError message
subscribedConfirmation of a subscribe request
unsubscribedConfirmation of an unsubscribe request
pongReply to an app-level {"type": "ping"} message

Limits

  • Max 5 concurrent WebSocket connections per user. Exceeding the cap on the first-message auth path triggers close code 4008; on the header-auth path the connection is rejected before upgrade with HTTP 429.
  • Message rate: 10 msg/s per connection sustained, burst of 20.
  • Liveness: the server sends a protocol-level WebSocket ping every 30s; a connection that doesn't pong within 60s is closed. Standard WS client libraries answer these automatically. The JSON {"type": "ping"} / pong exchange is a separate, optional app-level heartbeat — it does not count toward liveness.

Close codes

CodeMeaningRecover by
4001Auth timeout (no auth msg within 5s)Retry connection, send auth faster
4002Invalid token / API key, or insufficient scopeVerify credentials and that the key has trades:read
4008Connection limit exceededClose another connection first

Example (Python)

import websocket
import json
 
def on_open(ws):
    ws.send(json.dumps({"type": "auth", "api_key": "sprdr_..."}))
 
def on_message(ws, message):
    msg = json.loads(message)
    if msg["type"] == "authenticated":
        ws.send(json.dumps({
            "type": "subscribe",
            "data": {"subscription": "trades"}
        }))
    elif msg["type"] == "trade":
        print(f"Trade {msg['data']['trade_id']} -> {msg['data']['status']}")
    elif msg["type"] == "event":
        print(f"Event: {msg['data']['event_type']}")
 
ws = websocket.WebSocketApp(
    "wss://api.spreadr.xyz/api/v1/ws",
    on_open=on_open,
    on_message=on_message
)
ws.run_forever()

The frontend uses option 2 (first-message auth) with the Privy JWT so the token isn't logged in URL query parameters. If you're building a bot, prefer option 1 (header) with an API key — simpler and works the same in every WebSocket library.