Streaming Quickstart
This recipe walks through:
- Creating a stream-scoped API key
- Connecting to
/v1/stream - Subscribing to Hyperliquid
allMids+ Polymarket book on one connection - Handling pings and reconnecting cleanly
You'll need websocat or any WS client.
- Mint a stream-scoped key
bash
TOKEN=$(curl -sS -X POST https://api.artery.questflow.ai/keys \ -H "Content-Type: application/json" \ -d '{"name":"stream-recipe","userId":"u-1","scopes":["stream"]}' \ | jq -r .plaintext)The token must include the
streamscope or the WS upgrade returns403 forbidden_scope. - Connect
bash
websocat "wss://api.artery.questflow.ai/v1/stream?token=$TOKEN"Artery accepts the token via either the
tokenquery param or theAuthorization: Bearer <token>header. Query is convenient for browser WebSocket which can't set headers.On a successful upgrade you'll see no immediate frame — the server waits for your first
subscribe. - Subscribe to multiple providers
Channels are flat strings of the form
artery:stream:{provider}:{marketKey}. Use*to match every market under a provider/feed.json
{ "type": "subscribe", "channels": [ "artery:stream:hyperliquid_perp:*", "artery:stream:polymarket:0xCONDITION_ID" ] }Server reply:
json
{ "type": "subscribed", "subscription_id": "sub_01HMPK..." }From this point you'll receive
eventframes whenever the upstream provider emits a matching update. - Parse events
Every event carries
type = "<provider>.<native_event_type>":json
{ "type": "event", "subscription_id": "sub_01HMPK...", "data": { "type": "hyperliquid.allMids", "provider": "hyperliquid", "marketId": "*", "payload": { "BTC": "67542.0", "ETH": "3201.7", "100000421": "0.625" }, "receivedAt": 1714928400123 } }The
payloadis the upstream feed's raw shape. For HLallMids, keys are coin tickers + numeric asset ids (HIP-3 + HIP-4). For Polymarket book it's{ bids, asks, hash, market }. - Handle the heartbeat
Every 30 seconds the server pushes a JSON keepalive:
json
{ "type": "pong" }You can ignore it — the server only closes the connection (code
1001) if there's been no inbound frame from your client for >60s. Sending any subscribe / unsubscribe /{"type":"ping"}resets that timer. - Reconnect with backoff
On any close, reconnect with exponential backoff capped at 30s. Re-issue your subscriptions on the new connection — there is no server-side resumption (deliberately, to keep the gateway stateless).
ts
let delay = 500; while (true) { try { await connectAndSubscribe(); delay = 500; } catch (e) { await sleep(delay); delay = Math.min(delay * 2, 30_000); } }
Full Node.js example
jsimport WebSocket from 'ws';
const TOKEN = process.env.ART_TOKEN;
const ws = new WebSocket(`wss://api.artery.questflow.ai/v1/stream?token=${TOKEN}`);
ws.on('open', () => {
ws.send(
JSON.stringify({
type: 'subscribe',
channels: ['artery:stream:hyperliquid_perp:*'],
}),
);
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());
// Server pushes {type:"pong"} every 30s as a keepalive — ignore.
if (msg.type === 'pong') return;
if (msg.type === 'event') {
const mids = msg.data?.payload?.data ?? msg.data?.payload;
console.log('mid update:', Object.keys(mids ?? {}).length, 'assets');
}
});
ws.on('close', () => {
console.log('closed — reconnect with backoff');
});Subscription patterns
| You want | Channel string |
|---|---|
| All HL mids one update at a time | artery:stream:hyperliquid_perp:* |
| Single HL coin orderbook | artery:stream:hyperliquid_perp:BTC |
| HIP-4 outcome market | artery:stream:hyperliquid_perp:100000421 |
| Polymarket book by token id | artery:stream:polymarket:<token_id> |
| Kalshi market deltas (planned — needs RSA) | artery:stream:kalshi:<ticker> |
Kalshi streaming is RSA-gated. Even public Kalshi channels return HTTP 401 on connect unless
the handshake carries RSA-PSS signed headers. Artery will surface this as subscription_error
with code: rsa_required until M3-4 ships.
See also
- Streaming concept — wire protocol reference
- Streaming architecture — internals & leader-lock
- Cross-platform arbitrage — uses streaming for live spreads