Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.adipredictstreet.com/llms.txt

Use this file to discover all available pages before exploring further.

WebSocket delivery is at-least-once during normal operation. On disconnect or reconnect you must rebuild subscriptions and reconcile against REST.

sid lifecycle

sids are connection-local. They are NOT preserved across:
  • Network drops
  • Client-side WebSocket.close() + reopen
  • Server-side restarts
After every reconnect, run a fresh subscribe for everything you need. Old sids from the previous connection are meaningless.

Heartbeat

Send ping every 25 seconds (or whenever your TCP connection feels quiet):
{ "id": 999, "cmd": "ping" }
Server responds:
{ "id": 999, "type": "pong", "ts": 1776949200000 }
If you don’t get a pong within ~5 seconds, treat it as a dead connection — close and reconnect. ts is the server clock in ms. Use it as a clock-skew probe if you care about latency-sensitive timing on the client.

Reconnect strategy

let reconnectDelay = 1000;

function connect() {
  // Node / server: pass X-Api-Key as a header.
  // Browser:       use `${WSS}/ws/user?key=<encodeURIComponent(apiKey)>`
  //                because the WebSocket(…) ctor can't set custom headers.
  const ws = new WebSocket(`${WSS}/ws/user`, {
    headers: { 'X-Api-Key': apiKey },
  });

  ws.onopen = () => {
    reconnectDelay = 1000;
    // Always re-subscribe from scratch on a new connection.
    ws.send(JSON.stringify({
      id: nextId(),
      cmd: 'subscribe',
      params: { subscriptions: mySubscriptions },
    }));
  };

  ws.onclose = () => {
    // Drop sid → handler map; on next open you'll re-subscribe and
    // get fresh sids.
    sidHandlers.clear();
    setTimeout(connect, reconnectDelay);
    reconnectDelay = Math.min(reconnectDelay * 2, 30_000);
  };

  ws.onmessage = handleMessage;
}
After a successful reconnect-and-resubscribe, reconcile state from REST — don’t trust the WS to have replayed missed events from before the disconnect.

Channel-specific reconnect / resync

ChannelOn reconnect, also fetch
user_ordersGET /api/orders/open
user_fillsGET /api/me/fills?since=<lastSeen> (or GET /api/me/balances + GET /api/me/positions for absolute state)
vault_positionsGET /api/vaults/{addr}/positions
token_trade_matches / token_trade_settlementsGET /api/markets/{symbol}/trades?after=<lastSeenCreatedAt>&before=<now> to backfill missed fills (uses each trade’s createdAt as the cursor — that field stays immutable across status transitions). Omit after on first load to get the latest page.
token_bookre-subscribe → book_snapshot pushed automatically, OR call get_book_snapshot mid-session
token_ohlcGET /api/markets/{symbol}/ohlc?interval=s5&before=<now>
condition_lifecycleGET /api/markets/{symbol} for current status
systemGET /api/platform/status

Orderbook resync

token_book deltas carry seq and prevSeq. Track the last seq you applied per tokenId. If the next delta arrives with prevSeq != lastSeq, request a fresh snapshot — without dropping the existing subscription:
if (delta.prevSeq !== lastSeq[delta.id]) {
  // Gap — ask the gateway to re-push a snapshot on this sid.
  ws.send(JSON.stringify({
    id: nextId(),
    cmd: 'get_book_snapshot',
    params: { sid: delta.sid },
  }));
}
If the snapshot fetch fails the server pushes { "type": "book_snapshot_failed", "data": { "tokenId", "reason", "tsMs" } } on the same sid. Back off and retry, or fall back to GET /api/markets/{symbol}/orderbook?outcome=N&depth=100. In v1 the only way to force a fresh book_snapshot was an unsubscribe + resubscribe round-trip — get_book_snapshot removes that gap.

Auth on reconnect

Auth is validated at handshake only. If a key rotates mid-session, the open socket keeps working with the original credential until it closes. Close + reopen to pick up the new one.
ReasonClose codeWhat to do
api_key_revoked / api_key_bad_secret4401 <reason>Request a new key from your PredictStreet integration contact; don’t auto-retry a revoked key.
api_key_expired4401 api_key_expiredIssue a fresh key.
api_key_suspended4401 api_key_suspendedPartner is suspended. Contact ops.
api_key_unknown_key / api_key_bad_format4401 <reason>Token on disk is stale or malformed. Refresh from your secrets manager.
api_key_ip_denied4401 api_key_ip_deniedCaller IP isn’t in the key’s allowlist.
api_key_auth_disabled / api_key_auth_unconfigured4401 <reason>Server-side misconfig (feature flag off or pepper missing). Back off and alert.
forbidden origin1008 forbidden originWebSocket Origin header not in the allowlist.
Revoking a key closes every live socket bound to that keyId immediately via a Redis apikey:invalidate pub/sub — clients see the 4401 api_key_revoked frame within milliseconds.