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.
| Tier | Connections | Max Symbols per Connection |
|---|---|---|
| Free | 1 | 5 |
| Basic ($4.99/mo) | 5 | 25 |
| Pro ($9.99/mo) | 15 | 100 |
| Max ($24.99/mo) | 50 | Unlimited |
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:
| Code | Description |
|---|---|
INVALID_MESSAGE | Malformed JSON or invalid message format |
INVALID_CHANNEL | Unknown channel type in subscribe/unsubscribe |
SYMBOL_LIMIT_EXCEEDED | Too many symbols for the tier |
UNAUTHORIZED | Invalid API key (auth message) |
Reconnection
If the connection drops, implement exponential backoff reconnection:
- Wait 1 second, then reconnect
- If that fails, wait 2 seconds
- Then 4, 8, 16, 32 seconds (cap at 32s)
- 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())