Streaming (Pro)

Instead of polling /v2/odds, open one WebSocket and receive every price change as it happens. Streaming is available on the Pro plan.

  • Endpoint: wss://api.pingodds.com/v2/stream
  • Each subscribe counts as one request against your monthly allowance; the price updates themselves are free.
  • At most 5 open connections per API key.

1. Connect and authenticate

No header is needed to open the socket, so browsers can connect directly. Within 5 seconds, send your API key as the first message:

{"action":"auth","key":"po_live_…"}

The server answers:

{"type":"ready","plan":"pro","connection_id":"c_5152b34cb825b586"}

On a Free or Starter key it sends {"type":"error","code":"plan_required","message":"…","upgrade_url":"…"} and closes with 4403.

2. Subscribe

{"action":"subscribe","id":"s1","snapshot":true,
 "filters":[{"league":"americanfootball_nfl","books":["pinnacle","fanduel"],"markets":["spreads"]}]}

Each filter needs a league; books and markets are optional (omitted means all). Keys are the ones /v2/leagues, /v2/books and /v2/markets list; an unknown key gets an invalid_filter error and no subscription. With "snapshot": true you first get the current board, in the same shape as /v2/odds returns in events, then the acknowledgement:

{"type":"snapshot","id":"s1","events":[
  {"event":{"id":10,"league":"americanfootball_nfl","commence_time":"2026-09-27T17:00:00Z",
            "home":{"id":16,"name":"Cleveland Browns"},"away":{"id":17,"name":"Carolina Panthers"},
            "status":"scheduled"},
   "markets":[{"key":"spreads","books":[{"key":"fanduel","last_update":"2026-09-24T16:22:29.867Z",
     "last_checked":"2026-09-25T16:15:41.742Z","outcomes":[
       {"name":"home","line":2.5,"price_dec":1.9524,"price_am":-105,"implied":0.51219,"no_vig":0.489168,"suspended":false},
       {"name":"away","line":-2.5,"price_dec":1.8696,"price_am":-115,"implied":0.534874,"no_vig":0.510832,"suspended":false}]}]}]}
]}
{"type":"subscribed","id":"s1"}

Re-using an id replaces that subscription. {"action":"unsubscribe","id":"s1"} answers {"type":"unsubscribed","id":"s1"}, and {"action":"ping"} answers {"type":"pong"}.

3. Price updates

One message per outcome whose price or line changed:

{"type":"odds","event_id":88213,"league":"americanfootball_nfl","book":"pinnacle",
 "market":"spreads","outcome":"home","line":-3.5,"price_dec":1.909,"price_am":-110,
 "suspended":false,"ts":"2026-09-25T17:00:01Z"}

outcome is home/away for moneyline and spread, home/draw/away for a three-way moneyline (soccer), over/under for totals. Snapshots list outcomes in that order, home first. line is null for moneylines. ts is when PingOdds saw the change.

4. Errors and close codes

Errors that keep the connection open arrive as {"type":"error","id":"s1","code":"…","message":"…"} (id echoes the subscription when there is one): invalid_filter, unknown_subscription, bad_request, rate_limited, monthly_limit_reached, internal_error. The server closes the socket with:

CodeReasonWhat to do
4401unauthenticatedNo valid auth within 5 s, or the key is invalid or revoked. Fix the key.
4403plan_requiredThe account is not on Pro. Don't reconnect; upgrade.
4429too_many_connectionsThis key already has 5 open streams. Close one, or reconnect later.
4008slow_consumerYou fell 1,000 messages behind. Read faster or subscribe to less, then reconnect.
1011internal_errorServer-side failure. Reconnect with backoff.

For anything but 4401 and 4403, reconnect with exponential backoff (1 s, 2 s, 4 s … capped at 30 s) and subscribe again with "snapshot": true so you don't miss changes made while you were away.

5. Example client

// Node 22+ has a global WebSocket; in the browser the same code works as is.
const ws = new WebSocket("wss://api.pingodds.com/v2/stream");

ws.onopen = () => ws.send(JSON.stringify({ action: "auth", key: process.env.PINGODDS_API_KEY }));

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  switch (msg.type) {
    case "ready":
      ws.send(JSON.stringify({
        action: "subscribe", id: "nfl", snapshot: true,
        filters: [{ league: "americanfootball_nfl", markets: ["spreads", "totals"] }],
      }));
      break;
    case "snapshot": console.log("snapshot:", msg.events.length, "events"); break;
    case "odds":     console.log(msg.book, msg.event_id, msg.outcome, msg.line, msg.price_am); break;
    case "error":    console.error(msg.code, msg.message); break;
  }
};

ws.onclose = (e) => console.log("closed", e.code, e.reason); // reconnect with backoff unless 4403
setInterval(() => ws.readyState === 1 && ws.send('{"action":"ping"}'), 25_000);

Try it

Connects from this page straight to wss://api.pingodds.com/v2/stream. Your key stays in your browser.

  1. Messages appear here, newest first.