Skip to the guide

Build an odds comparison site

Match exact markets across bookmakers, reject stale prices and keep the board current with REST or streams.

What a valid comparison needs

The biggest price is not always the best price.

An odds comparison API is useful only when the event, period, market, selection, line and settlement contract match. Miss one field and a clean-looking table can recommend a different bet.

Prove equivalence first. Then rank the prices.

IdentityExact market and selection
FreshnessPer-book timestamp
CompletenessEvery requested book
ContinuitySnapshot and resume token

What the contract gives you

QuestionFieldsWhy it matters
Is it the same bet?market_group_id, selection_key, period, metric, line and sideThe prices belong in one comparison row.
Does it settle the same way?market_contract, selection_parametersSimilar source labels do not hide different rules.
How current is each book?bookmaker_as_of_ts_ms, target_refresh_interval_secondsFreshness can be shown per bookmaker, not guessed from one page timestamp.
Did every requested book arrive?complete, bookmakers_included, bookmaker_countsThe interface can distinguish complete data from a partial page.
Can updates continue?resume, stream delta and resyncThe board can recover after a disconnect.

The false-comparison test

These illustrative prices show why the line matters.

BookLineOddsSame bet?
Book A-2.51.95Yes · best
Book B-3.52.10No
Book C-2.51.91Yes

Naive resultBook B at 2.10Wrong. It ranks a different line.

Valid resultBook A at 1.95Correct for the -2.5 comparison group.

Build the comparison key first

Do not group by team name and market label. Match the fields that define the bet.

comparison_key.jsEXACT MATCH
  1. 01
    Eventevent_id
  2. 02
    Marketmarket_group_id · period · metric · line
  3. 03
    Selectionselection_key · side · player_name
  4. 04
    Contractmarket_contract · selection_parameters

Different key. Different comparison row.

Build it in six steps

Start with one event and a small bookmaker set.

  1. Choose one event

    Find a covered event and keep its canonical event_id.

  2. Request a complete snapshot

    Request the bookmakers and markets you need, then follow next_cursor until complete is true.

  3. Build an exact comparison key

    Match the event, market group, selection, period, metric, line, side and settlement contract.

  4. Reject unusable prices

    Remove unavailable prices, incomplete market groups and rows outside your freshness policy.

  5. Render the best valid price

    Rank prices only inside an exact comparison group and preserve ties.

  6. Keep the board current

    Start from the snapshot resume token, apply stream deltas and reload after resync.

Request a complete snapshot

Request three bookmakersServer-side
curl --request GET \
  --header "X-API-Key: $ODDS_API_KEY" \
  "https://api.odds-api.net/v1/events/{event_id}/odds/snapshot?bookmakers=pinnacle,draftkings,fanduel&market_keys=moneyline,handicap,total&periods=full%20time&include_unavailable=true&limit=2000"

If next_cursor is present, keep the filters unchanged until complete is true.

View the JavaScript grouping example
best-prices.jsNode.js
function stable(value) {
  if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
  if (value && typeof value === "object") {
    const pairs = Object.keys(value).sort()
      .map(key => `${JSON.stringify(key)}:${stable(value[key])}`);
    return `{${pairs.join(",")}}`;
  }
  return JSON.stringify(value ?? null);
}

function comparisonKey(eventId, row) {
  return [
    eventId,
    row.market_group_id,
    row.selection_key,
    row.period_str ?? row.period,
    row.bet_type,
    row.metric,
    row.line ?? "",
    row.side ?? "",
    row.player_name ?? "",
    stable(row.market_contract),
    stable(row.selection_parameters),
  ].join("|");
}

function bestValidPrices(snapshot) {
  const groups = new Map();

  for (const row of snapshot.items) {
    if (!row.is_available || typeof row.odds !== "number") continue;
    const key = comparisonKey(snapshot.event_id, row);
    const current = groups.get(key) ?? [];
    if (!current.length || row.odds > current[0].odds) groups.set(key, [row]);
    else if (row.odds === current[0].odds) current.push(row);
  }

  return [...groups.entries()].map(([key, books]) => ({
    key,
    best_odds: books[0].odds,
    books,
  }));
}

Run this on your backend. Never expose ODDS_API_KEY in browser JavaScript.

Show freshness per bookmaker

Use bookmaker_as_of_ts_ms for each book. Compare it with the current time and target_refresh_interval_seconds.

Current

Inside your threshold. Show the price normally.

Ageing

Show its age and stop marking it as best.

Unavailable

Keep the cell visible but inactive.

Do not hide missing data.

An empty cell means that book has no comparable available price.

Snapshot first. Stream when it matters.

A stream is not a complete board. Load the REST snapshot, save resume, then apply deltas.

The production loop
  1. 01SnapshotLoad complete rows
  2. 02Save resumeKeep the cursor
  3. 03StreamSSE or WebSocket
  4. 04Apply deltasUpdate exact keys
  5. 05ResyncReload after resync

Use SSE for one-way updates or WebSockets where they fit your stack. Both use the same update contract.

Read the REST, SSE and WebSocket guide.

Lay out the comparison screen

Show the bet, the best valid price and its age without a tooltip.

Illustrative interfaceLakers at Celtics3 books · updated 8s ago
Full gamePoint spreadDecimal odds
SelectionDraftKingsFanDuelPinnacle
Lakers -2.51.91 6s1.95 8s · best1.93 12s
Celtics +2.51.91 6s1.88 8s1.96 12s · best
Rows Exact selection and lineColumns One bookmaker eachMobile Scroll the bookmaker columns

Use the API commercially

Paid plans support customer-facing comparison products. Pre-match odds only. Your product owns the interface and customer experience.

PrototypeStarter · US$30/month

50,000 credits

Public siteBuilder · US$90/month

2 million credits

Higher trafficLive · US$250/month

20 million credits

Raw-feed resale, bulk redistribution and API-key sharing are not included. Read the API Terms and Data License before launch, then use the current pricing limits to model requests and stream hours.

Compare the bet before the price

Match the event, market, line and settlement contract. Check freshness. Then highlight the best price.

Odds comparison API questions

Can I use odds-api.net to build an odds comparison site?

Yes. Paid plans support customer-facing odds comparison sites using covered pre-match bookmaker prices. Keep the API key on your server and follow the data licence. Raw-feed resale, bulk redistribution and API-key sharing are not included.

How do I compare the same bet across bookmakers?

Compare only rows with the same event, market group, selection, period, metric, line, side and settlement contract. A point spread of -2.5 is not the same bet as -3.5, even when both rows name the same team.

How do I know whether every requested bookmaker is included?

Use complete, bookmakers_included and bookmaker_counts on the snapshot response. When next_cursor is present, keep the filters unchanged and request the next page before treating the board as complete.

How fresh are the odds for each bookmaker?

Read bookmaker_as_of_ts_ms for each bookmaker included in the response. Compare it with the current time and target_refresh_interval_seconds. as_of_ts_ms describes the assembled snapshot, not each bookmaker's individual acceptance time.

Should an odds comparison site poll or stream?

Load a complete REST snapshot first. Use SSE or WebSockets for supported pre-match changes, save the latest resume token and load a new snapshot whenever the stream sends resync.

Which plan suits an odds comparison website?

Builder is the usual starting point for a public comparison product because it includes 2 million base API credits and eight concurrent streams. A smaller prototype may fit Starter. Calculate the events, refresh rate and stream hours before choosing.

Does odds-api.net provide in-play sportsbook odds?

No. The general sportsbook product supplies pre-match odds. Do not design a live in-play promise around this feed.

Sources and method

The contract study checked the live odds-api.net OpenAPI document, coverage catalogue, pricing and public developer package on 15 September 2026. The point-spread comparison is a controlled illustrative example, not a record of live bookmaker prices.