# Migrate from OpticOdds or OddsJam API: SSE to WebSocket

> Move an OpticOdds or OddsJam API workflow to TheRundown: map fixtures and sources, change SSE to WebSocket, and validate snapshots, live IDs and usage.

Published 2026-10-13. Updated 2026-10-13. Canonical: https://therundown.io/blog/migrate-from-opticodds

Choose TheRundown when public self-serve pricing, odds and scores under shared event IDs, and a scoped WebSocket integration fit your application. OpticOdds publishes SSE streaming and delivery in under 800ms; this guide does not claim a measured latency advantage over it. Validate the actual books and markets your application needs before changing feeds.

This TheRundown-authored guide was checked against primary documentation on October 13, 2026. For an OddsJam API integration, confirm which API/version your contract uses; the OpticOdds V3 examples below describe the documented OpticOdds SSE path, not every legacy OddsJam contract.

## Map fixtures, sources and markets before transport

| Existing OpticOdds V3 workflow       | TheRundown V2 workflow                      | Check                                              |
| ------------------------------------ | ------------------------------------------- | -------------------------------------------------- |
| Sport/league and fixture identifiers | Numeric sport ID and canonical `event_id`   | Build an explicit provider event crosswalk         |
| Sportsbook names                     | Numeric `affiliate_ids`                     | Read the current source catalog                    |
| Named market filters                 | Numeric `market_ids`                        | Preserve period, participant, side and signed line |
| `/stream/odds/{sport}` HTTP SSE      | `/api/v2/ws` WebSocket                      | Replace the transport client and message parser    |
| SSE `odds` / `locked-odds` events    | V2 market frames and exact quote state      | Do not equate timestamps with availability         |
| SSE `last_entry_id`                  | TheRundown-specific snapshot/recovery state | Cursors are provider-specific                      |

OpticOdds's documented stream uses named filters, a `key` parameter and `last_entry_id`. Preserve its explicit locked-quote state while validating both feeds. On TheRundown, price identity includes event, market, participant type/ID, signed line and source; `closed_at` closes that exact quote and a later empty value reopens it. An old update timestamp does not itself close a price.

## Establish scope and access

TheRundown Free lets you inspect delayed pre-match main lines from DraftKings, FanDuel and BetMGM without a card. It does not permit WebSocket. Starter is $49/month with a 60-second odds delay, Pro $149 with a 30-second delay and commercial display rights, and Ultra $399 with real-time REST/WebSocket. Higher-volume plans are public on [API pricing](/pricing/api#volume-plans).

OpticOdds and OddsJam API use sales onboarding rather than a verified public numeric API price. Compare your contract, scope and rights with the proposed TheRundown plan; the public $399 figure alone does not establish a lower price for every contract or equivalent coverage.

Read [sports](https://docs.therundown.io/reference/sports), [sources](https://docs.therundown.io/reference/sportsbooks) and [market IDs](https://docs.therundown.io/reference/markets). The example below uses NBA regular-season sport 4 and three shared US books. Change the date and scope to actual upcoming events. An empty dated snapshot is valid.

## Connect and inspect a bounded snapshot

Use a server-side Node.js process with the `ws` package installed. Set `THERUNDOWN_API_KEY` and `EVENT_DATE` in the environment. This is a protocol inspection example: it requests a bounded snapshot and logs message types/price identifiers. It does not implement a production cache, retry loop or replay adapter.

```javascript
const WebSocket = require('ws');
const key = process.env.THERUNDOWN_API_KEY;
const date = process.env.EVENT_DATE;
if (!key || !/^\d{4}-\d{2}-\d{2}$/.test(date || '')) {
  throw new Error('Set THERUNDOWN_API_KEY and EVENT_DATE (YYYY-MM-DD)');
}
const socket = new WebSocket('wss://therundown.io/api/v2/ws', {
  headers: { 'X-TheRundown-Key': key },
});
socket.on('open', () =>
  socket.send(
    JSON.stringify({
      action: 'subscribe',
      id: 'nba-main-lines',
      channel: 'markets',
      params: {
        sport_ids: [4],
        date,
        affiliate_ids: [19, 22, 23],
        market_ids: [1, 2, 3],
        main_line: true,
        snapshot: true,
      },
    }),
  ),
);
socket.on('message', (raw) => {
  const message = JSON.parse(raw.toString());
  if (
    message.type === 'delta' &&
    message.id === 'nba-main-lines' &&
    message.data?.meta?.type === 'market_price'
  ) {
    const row = message.data.data;
    console.log({
      event: row.event_id,
      market: row.market_id,
      participant_type: row.normalized_market_participant_type,
      participant: row.normalized_market_participant_id,
      line: row.line,
      book: row.affiliate_id,
      price: row.price,
    });
  } else {
    console.log('Message type:', message.type || message.meta?.type);
  }
});
socket.on('error', () => console.error('WebSocket connection failed'));
socket.on('close', (code) => console.log('Connection closed:', code));
```

The server sends the subscription acknowledgment, scoped snapshot frame(s), then `snapshot_complete` before deltas. A production consumer must parse and store those snapshots; this logger only identifies them. Send the key in the upgrade header. Native browser WebSocket cannot set that header, so browser clients need your authenticated backend relay.

## Adapt identity and recovery deliberately

Use normalized participant identity from the stream, not the sportsbook-specific `market_participant_id`. Full-game live odds retain market IDs **41/42/43**; a 1/2/3 subscription includes them server-side. Accept both families in the client and keep periods distinct. Do not silently discard live frames by matching only prematch IDs.

Ultra permits one connection and three multiplexed subscriptions. Keep handlers short, monitor heartbeats and reconnect after closure with bounded backoff. On gaps or overflow, restore current state from a scoped snapshot/REST read according to the [WebSocket guide](https://docs.therundown.io/api-reference/v2/websocket) before continuing. Do not pass OpticOdds cursors into TheRundown.

For an authoritative current main line, also refresh scoped REST `main_line=true`: a designation-only move surfaces in the stream on that line's next price update. Validate closures and reopens against the current API response rather than assuming every message is an open quote.

## Compare usage, not SSE event counts

TheRundown meters delivered changed price rows; snapshots cost the corresponding returned records. A snapshot of ten games with three two-sided markets at three books, plus each event and score and no `live_game_state`, illustrates **200 data points**. Actual returned rows and `X-Datapoints` determine REST usage. Ten changed price rows are a different workload from ten full snapshots.

Paid subscriptions have a fixed fee and included volume. Restrict sports, sources, events and markets to what you consume; unrequested rows conserve the allowance, but the base fee remains payable. Do not multiply an old SSE event count by an assumed universal factor. See [metering](https://docs.therundown.io/rate-limits).

## Validate before switching traffic

Compare one moneyline, both spread sides, totals, market periods, a missing source, a closed quote and a reopen. Check effective freshness and any required bet-slip links or SGP functionality separately. Keep the existing feed running until your event crosswalk, market parser, snapshot/recovery handling and rights checks pass.

Read the [OpticOdds comparison](/compare/opticodds), [OddsJam API comparison](/compare/oddsjam) and [live odds integration guide](/live-odds-api) for package and protocol context.

## Sources checked October 13, 2026

- [OpticOdds V3 SSE specification](https://developer.opticodds.com/reference/get_stream-odds-sport)
- [OpticOdds published delivery claim](https://opticodds.com/sports-betting-api)
- [OpticOdds pricing access](https://opticodds.com/pricing)
- [OddsJam API access](https://oddsjam.com/odds-api)
- [TheRundown data model](https://docs.therundown.io/reference/data-model)
- [TheRundown WebSocket specification](https://docs.therundown.io/api-reference/v2/websocket)

## Questions

### Can I replace an OpticOdds SSE URL with a TheRundown WebSocket URL?

No. HTTP SSE and WebSocket have different clients and message formats. Map identifiers and markets, authenticate the server-side upgrade, scope subscriptions and implement snapshot/reconnect handling.

### Does TheRundown cost less than an OpticOdds or OddsJam API contract?

TheRundown Ultra is publicly listed at $399 monthly with real-time REST and WebSocket. OpticOdds and OddsJam API use sales onboarding without a verified public numeric API quote. Compare your actual contract and required package; a universal lower-price claim is not established.

### Should I filter live market frames to IDs 1, 2 and 3?

The server includes the corresponding live variants when filtering main markets 1, 2 and 3, but live full-game frames retain IDs 41, 42 and 43. A client accepting only the prematch IDs would discard those live updates.

### Does WebSocket mean unlimited usage?

No. Changed rows and snapshots are metered, and connection/subscription caps apply. Ultra includes one connection and three multiplexed subscriptions. TheRundown paid plans have a fixed fee with included volume; scoped reads conserve that allowance.
