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.
4001).Any value from GET /v1/sports works. Three common picks:
baseball_mlb — MLB game lines + 40+ player propsbasketball_nba — NBA game lines + 60+ player propsicehockey_nhl — NHL game lines + SOG / PPP / blocksThe 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.
You'll see three frame types arrive in order:
connected within ~1 ms (tier, push_mode, coalesce window)initial_state within ~200 ms (up to 500 current rows)odds_update every time a price changes (silent if nothing's moving)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 */
}
});
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())
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);
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).
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.
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.
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.