Market.ai WebSocket Push API Guide

Real-time market data streaming via WebSocket. Subscribe to channels and receive updates as they happen -- quotes, trades, orderbook changes, signals, liquidations, funding rates, klines, narratives, and Polymarket prediction markets.

Connection

Connect to the WebSocket server with your API key as a query parameter:

wss://ws.market-ai.dev?key=mk_live_abc123...

The server validates the API key during the upgrade handshake. If the key is missing or invalid, the connection is rejected with an HTTP error before the WebSocket handshake completes.


Authentication

There are two ways to authenticate:

Option 1: Query parameter (recommended)

Pass the API key in the connection URL:

wss://ws.market-ai.dev?key=mk_live_abc123...

Option 2: Auth message

Connect without a key and send an auth message as the first frame:

{"action": "auth", "key": "mk_live_abc123..."}

Server responds:

{"type": "authenticated", "tier": "basic"}

Tier Limits

All tiers include WebSocket access.

TierConnectionsMax Symbols per Connection
Free15
Basic ($4.99/mo)525
Pro ($9.99/mo)15100
Max ($24.99/mo)50Unlimited

If you exceed the connection limit for your tier:

{
  "ok": false,
  "error": {
    "code": "CONNECTION_LIMIT",
    "message": "Connection limit (2) reached for your tier."
  }
}

Protocol

All messages are JSON. The client sends actions; the server sends typed responses and data updates.

Subscribe

Subscribe to one or more channels with optional symbol filters:

{
  "action": "subscribe",
  "channels": [
    {"type": "quotes", "symbols": ["BTC/USDT", "ETH/USDT"]},
    {"type": "signals"},
    {"type": "trades", "symbols": ["BTC/USDT"]}
  ]
}

If symbols is omitted or empty, the channel subscribes to all available data for that channel type (using a wildcard *).

Server confirms each channel subscription:

{"type": "subscribed", "channel": "quotes", "symbols": ["BTC/USDT", "ETH/USDT"]}
{"type": "subscribed", "channel": "signals", "symbols": ["*"]}
{"type": "subscribed", "channel": "trades", "symbols": ["BTC/USDT"]}

If subscribing would exceed your symbol limit, the server subscribes as many as possible and reports the rejected ones:

{"type": "error", "code": "SYMBOL_LIMIT_EXCEEDED", "message": "Symbol limit (25) exceeded. Rejected: SOL/USDT, XRP/USDT"}

Unsubscribe

{
  "action": "unsubscribe",
  "channels": [
    {"type": "quotes", "symbols": ["ETH/USDT"]}
  ]
}

Server confirms:

{"type": "unsubscribed", "channel": "quotes", "symbols": ["ETH/USDT"]}

Ping/Pong

The server sends protocol-level pings every 30 seconds. If no pong is received within 10 seconds, the connection is closed.

You can also send application-level pings:

{"action": "ping"}

Server responds:

{"type": "pong"}

Channels

All data updates follow this format:

{
  "type": "update",
  "channel": "<channel_name>",
  "ts": 1708776300000,
  "data": { ... }
}

quotes

Aggregated cross-exchange quotes, updated in real-time.

{
  "type": "update",
  "channel": "quotes",
  "ts": 1708776300000,
  "data": {
    "symbol": "BTC/USDT",
    "global_mid": "67450.25",
    "global_vwap": "67448.10",
    "max_arb_bps": 3.2,
    "consensus_trend": "bullish",
    "exchanges": [
      {"exchange": "binance", "bid": "67449.50", "ask": "67450.80", "mid": "67450.15", "volume_24h": "12500.5", "obi": 0.12}
    ]
  }
}

book

Analyzed orderbook updates with walls, OBI, depth, and spread.

{
  "type": "update",
  "channel": "book",
  "ts": 1708776300000,
  "data": {
    "symbol": "BTC/USDT",
    "exchange": "binance",
    "best_bid": "67449.50",
    "best_ask": "67450.80",
    "mid_price": "67450.15",
    "spread_bps": 1.93,
    "obi_5": 0.15,
    "obi_10": 0.08,
    "bid_depth_1pct": "125.5",
    "ask_depth_1pct": "98.2",
    "bid_walls": [{"price": "67000.00", "quantity": "45.2", "multiple_of_median": 8.5}],
    "ask_walls": []
  }
}

trades

Normalized trade events from exchanges.

{
  "type": "update",
  "channel": "trades",
  "ts": 1708776300000,
  "data": {
    "exchange": "binance",
    "symbol": "BTC/USDT",
    "price": "67450.00",
    "quantity": "0.5",
    "side": "buy",
    "timestamp": 1708776300000,
    "trade_id": "t123456",
    "is_liquidation": false
  }
}

signals

Market intelligence signals -- whale movements, liquidation cascades, funding spikes, and more.

{
  "type": "update",
  "channel": "signals",
  "ts": 1708776300000,
  "data": {
    "id": "sig_abc123",
    "signal_type": "whale_movement",
    "symbol": "BTC/USDT",
    "severity": "alert",
    "data": {"exchange": "binance", "size_usd": 2500000, "side": "buy"},
    "timestamp": 1708776300000,
    "narrative": "Whale buy detected on Binance: $2.5M BTC market order"
  }
}

Signal types: whale_movement, liquidation_cascade, funding_spike, orderbook_imbalance, spread_anomaly, volume_spike, cross_exchange_divergence, wall_appearance, wall_removal, price_level_break

Severity levels: info, warning, alert, critical

liquidations

Forced liquidation events from exchanges.

{
  "type": "update",
  "channel": "liquidations",
  "ts": 1708776300000,
  "data": {
    "exchange": "binance",
    "symbol": "BTC/USDT",
    "side": "sell",
    "price": "67200.00",
    "quantity": "2.5",
    "timestamp": 1708776300000,
    "notional_usd": "168000.00"
  }
}

funding

Perpetual futures funding rate updates.

{
  "type": "update",
  "channel": "funding",
  "ts": 1708776300000,
  "data": {
    "exchange": "binance",
    "symbol": "BTC/USDT",
    "rate": 0.0001,
    "next_funding_time": 1708790400000,
    "predicted_rate": 0.00012
  }
}

klines

OHLCV candlestick updates.

{
  "type": "update",
  "channel": "klines",
  "ts": 1708776300000,
  "data": {
    "exchange": "binance",
    "symbol": "BTC/USDT",
    "interval": "1m",
    "open": "67440.00",
    "high": "67455.00",
    "low": "67435.00",
    "close": "67450.00",
    "volume": "12.5",
    "open_time": 1708776240000,
    "close_time": 1708776300000,
    "is_closed": true
  }
}

narratives

Natural language market narratives generated from template engine or LLM.

{
  "type": "update",
  "channel": "narratives",
  "ts": 1708776300000,
  "data": {
    "symbol": "BTC/USDT",
    "narrative": "BTC rallying 2.1% in the past hour with strong buying pressure. OBI at 0.18 across exchanges. A significant bid wall at $67,000 (45.2 BTC, 8.5x median) is supporting price. Funding rate normal at 0.01%.",
    "source": "template"
  }
}

polymarket

Polymarket crypto Up/Down prediction market updates.

{
  "type": "update",
  "channel": "polymarket",
  "ts": 1708776300000,
  "data": {
    "coin": "BTC",
    "timeframe": "1h",
    "upPrice": "0.545",
    "downPrice": "0.455",
    "volume": "25000.50",
    "liquidity": "12000.00",
    "timeRemainingSeconds": 3300,
    "active": true
  }
}

Error Handling

Errors are sent as JSON messages:

{
  "type": "error",
  "code": "INVALID_MESSAGE",
  "message": "Invalid JSON"
}

Error codes:

CodeDescription
INVALID_MESSAGEMalformed JSON or invalid message format
INVALID_CHANNELUnknown channel type in subscribe/unsubscribe
SYMBOL_LIMIT_EXCEEDEDToo many symbols for the tier
UNAUTHORIZEDInvalid API key (auth message)

Reconnection

If the connection drops, implement exponential backoff reconnection:

  1. Wait 1 second, then reconnect
  2. If that fails, wait 2 seconds
  3. Then 4, 8, 16, 32 seconds (cap at 32s)
  4. Reset the backoff counter after 60 seconds of stable connection

After reconnecting, re-send all subscribe messages. The server does not remember your subscriptions from previous connections.


Examples

JavaScript/TypeScript (Bun or Node.js)

const ws = new WebSocket("wss://ws.market-ai.dev?key=mk_live_abc123...");

ws.onopen = () => {
  ws.send(JSON.stringify({
    action: "subscribe",
    channels: [
      { type: "quotes", symbols: ["BTC/USDT", "ETH/USDT"] },
      { type: "signals" },
    ],
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case "subscribed":
      console.log(`Subscribed to ${msg.channel}: ${msg.symbols}`);
      break;
    case "update":
      console.log(`[${msg.channel}] ${JSON.stringify(msg.data)}`);
      break;
    case "pong":
      break;
    case "error":
      console.error(`Error: ${msg.code} - ${msg.message}`);
      break;
  }
};

// Keep alive with pings
setInterval(() => {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send(JSON.stringify({ action: "ping" }));
  }
}, 25000);

Python

import asyncio
import json
import websockets

async def connect():
    uri = "wss://ws.market-ai.dev?key=mk_live_abc123..."
    async with websockets.connect(uri) as ws:
        # Subscribe
        await ws.send(json.dumps({
            "action": "subscribe",
            "channels": [
                {"type": "quotes", "symbols": ["BTC/USDT"]},
                {"type": "signals"}
            ]
        }))

        # Listen for messages
        async for message in ws:
            msg = json.loads(message)
            if msg["type"] == "update":
                print(f"[{msg['channel']}] {json.dumps(msg['data'], indent=2)}")

asyncio.run(connect())