WebSocket Quickstart

Live odds streaming into your terminal in under 60 seconds. Copy any block below, set your API key, and run it. No build step, no SDK install for Node and Python beyond a single dependency.

Reference Quickstart All examples Player props Edge alerts Troubleshooting SSE
Before you start. You need a Business, Enterprise, or Scale API key. Upgrade here or grab a 24-hour Scale trial via the dashboard. Free / Starter / Pro keys are rejected at handshake (close code 4001).

Step 1 — Pick a sport

Any value from GET /v1/sports works. Three common picks:

Step 2 — Connect

The URL pattern is:

wss://parlay-api.com/v1/ws/odds/{sport_key}?apiKey=YOUR_KEY

Use /ws/live/ instead of /ws/odds/ for live-game-only updates.

Step 3 — Read frames

You'll see three frame types arrive in order:

  1. connected within ~1 ms (tier, push_mode, coalesce window)
  2. initial_state within ~200 ms (up to 500 current rows)
  3. odds_update every time a price changes (silent if nothing's moving)

Copy-paste — Node.js

Single dependency, single file.

npm install ws
# save the snippet below as stream.mjs, then:
# PARLAY_API_KEY=pk_live_xxxx node stream.mjs
// stream.mjs
import WebSocket from "ws";

const KEY  = process.env.PARLAY_API_KEY;
const URL  = `wss://parlay-api.com/v1/ws/odds/basketball_nba?apiKey=${KEY}`;
const ws   = new WebSocket(URL);

ws.on("open",    () => console.log("[open] connected"));
ws.on("close",   (code) => console.log(`[close] ${code}`));
ws.on("error",   (err)  => console.error("[error]", err.message));
ws.on("message", (buf) => {
  const frame = JSON.parse(buf.toString());
  if (frame.type === "connected") {
    console.log(`[connected] tier=${frame.tier} push_mode=${frame.push_mode}`);
  } else if (frame.type === "initial_state") {
    console.log(`[snapshot] ${frame.count} rows`);
  } else if (frame.type === "odds_update") {
    for (const row of frame.data.slice(0, 3)) {
      console.log(`[update] ${row.bookmaker} ${row.market_key} ${row.outcome} -> ${row.price_american}`);
    }
  } else if (frame.type === "heartbeat") {
    /* keepalive */
  }
});

Copy-paste — Python (websockets)

pip install websockets
# PARLAY_API_KEY=pk_live_xxxx python stream.py
# stream.py
import asyncio, json, os, websockets

KEY = os.environ["PARLAY_API_KEY"]
URL = f"wss://parlay-api.com/v1/ws/odds/basketball_nba?apiKey={KEY}"

async def consume():
    async with websockets.connect(URL, ping_interval=20, ping_timeout=20) as ws:
        async for raw in ws:
            frame = json.loads(raw)
            if frame["type"] == "connected":
                print(f"[connected] tier={frame['tier']} push_mode={frame['push_mode']}")
            elif frame["type"] == "initial_state":
                print(f"[snapshot] {frame['count']} rows")
            elif frame["type"] == "odds_update":
                for row in frame["data"][:3]:
                    print(f"[update] {row['bookmaker']:<12} {row['market_key']:<10} "
                          f"{row['outcome']:<28} -> {row['price_american']:+d}")

asyncio.run(consume())

Copy-paste — Browser (vanilla JS)

Paste into any HTML page or browser DevTools console. Browsers can't set custom headers on WebSocket, so we use the query-param auth form.

const KEY = "pk_live_xxxx";  // ⚠️ Don't commit this. Use a backend proxy in production.
const ws  = new WebSocket(`wss://parlay-api.com/v1/ws/odds/basketball_nba?apiKey=${KEY}`);

ws.onmessage = (ev) => {
  const frame = JSON.parse(ev.data);
  if (frame.type === "odds_update") {
    console.log(`${frame.count} rows updated at`, new Date(frame.timestamp * 1000));
  } else {
    console.log(frame.type, frame);
  }
};
ws.onclose = (e) => console.log("closed:", e.code, e.reason);
Don't ship your key in a browser bundle. Anyone who views the source can read and abuse it. For browser-side use, run a small backend proxy that holds the key and re-exposes the stream to your front-end on a per-user session.

Three common use cases

1) Watch line movement for a single game

Send a subscribe frame immediately after the connected envelope:

ws.on("open", () => {
  // Once connected envelope lands, filter to one event:
  setTimeout(() => {
    ws.send(JSON.stringify({
      type: "subscribe",
      event_id: "2026-05-13_Boston_Celtics_New_York_Knicks"
    }));
  }, 50);
});

From that point on, your socket only delivers updates for that game. To watch a second game on the same connection, send a new subscribe (the filter is single-event — for parallel watch, open multiple connections).

2) Edge alerts on price drops

Cache the previous price per (event, book, market, outcome). On each update, fire an alert when the new price is sharper than your threshold. The full pattern is on the edge-alerts page.

3) Backend for a dashboard

Hold one WebSocket per sport server-side. On each odds_update, push to your own front-end via a separate channel (SSE, Socket.IO, etc.). This is the right architecture if you have more than a handful of dashboard users — one upstream connection per sport, not one per user.

Smoke test with curl

If you only want to verify the handshake works without writing code:

curl --include \
  --no-buffer \
  --header "Connection: Upgrade" \
  --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Key: $(head -c 16 /dev/urandom | base64)" \
  --header "Sec-WebSocket-Version: 13" \
  --header "X-API-Key: $PARLAY_API_KEY" \
  "https://parlay-api.com/v1/ws/odds/baseball_mlb"

A successful handshake returns HTTP/1.1 101 Switching Protocols. Anything else (403, 401, 400) means your key, tier, or headers are off. See troubleshooting.

Next steps