Guides · 12 October 2026 · updated 14 October 2026

How to Build an Odds Comparison Site in Australia

Map every bookmaker's price to one market, rank only like with like, and stay inside Australian advertising law.

To build an odds comparison site in Australia you need three things. First, prices from licensed Australian bookmakers mapped into one shape, so "Swans to win" means the same market at every book. Second, rules that only rank like with like: the same line, the same period and a price that is recent. Third, a site that stays inside Australian law: list licensed bookmakers only, publish no sign-up inducements, and don't send readers to bet online during play.

This guide works through each part, with a worked example of how a comparison changes the margin a reader sees.

What an odds comparison site does

For every event, market and outcome, the site shows each bookmaker's price side by side, highlights the best one, and links the reader to that book. Under the table sit four jobs:

JobWhat it needs
CollectEvery book's price for the same market, in one format
MatchPrices grouped by market, period, side and line
RankRecent prices only, sorted by decimal odds
Link outA route to the chosen bookmaker's own site

Most of the work is in matching and in deciding which prices to trust, not in drawing the table.

Step 1: get every book's price into one shape

Each bookmaker names its markets differently. One calls it "Head to Head", another "Match Result", a third "To Win". Scraping each book yourself means writing and maintaining a mapping for every one of them.

An odds API does the mapping for you. In SockOdds API v1, a market's id is built from the event, the market type, its subject, its period and its line, so the same market has the same id at every bookmaker. Each outcome has an id that is shared in the same way. A response groups the prices by market, as Reading prices sets out:

  • the event, with its status and scheduled_start (UTC)
  • markets, each listed once, with id, key (winner, handicap, total_points and so on), name, period, subject and alternate
  • outcomes, each listed once, with id, name, side, line and prices
  • prices, one per bookmaker and best first, each with the bookmaker's key (such as sportsbet) in bookmaker, plus decimal_odds, implied_probability, available and observed_at

That is already the shape of a comparison row: one outcome, every bookmaker's price beside it.

One request covers a whole league: GET /v1/odds?league=nfl&regions=au&markets=winner,handicap,total_points returns the upcoming games with every Australian bookmaker's prices for those three markets. For one game you already know, GET /v1/events/{event_id}/odds takes the same filters.

Which bookmakers you see depends on the plan. The free Developer plan includes Sportsbet, TAB and Ladbrokes. Rookie includes Sportsbet, TAB, Ladbrokes, Neds, PointsBet, Unibet, Betfair Exchange, Dabble and Betr. Pro includes every supported bookmaker. Bookmakers outside your plan are left out of responses rather than refused, so code the table to cope with a missing column. The pricing page has the details.

Step 2: only compare like with like

A best-price table is only useful if every price in a row is the same bet. Three checks cover most of the mistakes.

Same line and same period

On line and total markets, two books can offer different lines. Sydney -12.5 at 1.90 and Sydney -10.5 at 1.80 are different bets: the second is easier to win, so it pays less. Putting them in one row makes the -12.5 price look "best" when it isn't comparable.

Because the line and the period are part of the market id, and the response lists each market id once with its own prices, -12.5 and -10.5 stay apart without any extra work, and a full head to head (golden point or extra time included) stays apart from a regulation result. Show the line in the cell next to the price. Alternate line markets carry alternate: true and only arrive when you ask with alternates=true, so the main table stays on each book's main line by default.

Current prices only

Books suspend markets for team news, late changes and the start of play. SockOdds leaves a suspended or withdrawn selection out of current responses until the book offers it again, so you never rank a price that has been pulled. The flip side is that a cell can be empty. A missing price means there is no current quote to show, never that the price is zero or that the market is closed. Render the gap as a dash or a blank, and don't let it break the row.

Recent prices only

A price is a snapshot. Every price carries observed_at, the time SockOdds saw that price at the bookmaker, in UTC. A quote not seen again within ten minutes is withheld from current responses rather than shown as current. No refresh interval is promised, so read the timestamp rather than assuming one.

Show readers how old each price is ("seen 2 min ago") rather than calling the table "live". It's honest, and it explains why a price may have moved by the time they reach the bookmaker.

This program reads one NFL game's head to head, line and total from every Australian bookmaker. Prices arrive grouped by market, so different lines never share a row, and each outcome's prices are sorted best first. The program prints the best price for each outcome with what the best prices add up to:

const url = "https://api.sockodds.com/v1/events/ev_000ckk0ee4ze/odds?regions=au&markets=winner,handicap,total_points";
const response = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` } });
const { data: event } = await response.json();
const bookmakers = new Set(event.markets.flatMap((m) => m.outcomes.flatMap((o) => o.prices.map((p) => p.bookmaker))));
console.log(`${event.name}: ${bookmakers.size} Australian bookmakers`);

// Each market is one id, which includes the period and the line, so different lines never share a row.
// Each outcome's prices are sorted best first, so prices[0] is the best price.
for (const market of event.markets) {
  const book = market.outcomes.reduce((sum, o) => sum + 1 / o.prices[0].decimal_odds, 0);
  console.log(`${market.name}: best prices add up to ${(book * 100).toFixed(2)}%`);
  for (const o of market.outcomes) {
    const best = o.prices[0];
    const label = o.line === null ? o.name : `${o.name} ${o.line}`;
    console.log(`  ${label.padEnd(24)} ${best.decimal_odds.toFixed(2)}  ${best.bookmaker}`);
  }
}
import json, os, urllib.request

url = "https://api.sockodds.com/v1/events/ev_000ckk0ee4ze/odds?regions=au&markets=winner,handicap,total_points"
request = urllib.request.Request(url, headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]})
with urllib.request.urlopen(request) as response:
    event = json.load(response)["data"]
bookmakers = {p["bookmaker"] for m in event["markets"] for o in m["outcomes"] for p in o["prices"]}
print(f"{event['name']}: {len(bookmakers)} Australian bookmakers")

# Each market is one id, which includes the period and the line, so different lines never share a row.
# Each outcome's prices are sorted best first, so prices[0] is the best price.
for market in event["markets"]:
    book = sum(1 / o["prices"][0]["decimal_odds"] for o in market["outcomes"])
    print(f"{market['name']}: best prices add up to {book * 100:.2f}%")
    for o in market["outcomes"]:
        best = o["prices"][0]
        label = o["name"] if o["line"] is None else f"{o['name']} {o['line']}"
        print(f"  {label.ljust(24)} {best['decimal_odds']:.2f}  {best['bookmaker']}")

Output from a local copy of the API with invented test prices

Arizona Cardinals v Atlanta Falcons: 8 Australian bookmakers
Winner: best prices add up to 100.09%
  Arizona Cardinals        1.81  betblitz
  Atlanta Falcons          2.23  betblitz
Handicap: best prices add up to 100.00%
  Arizona Cardinals -3.5   2.00  betblitz
  Atlanta Falcons 3.5      2.00  betblitz
Total points: best prices add up to 100.28%
  Over 45.5                1.96  betblitz
  Under 45.5               2.03  betblitz

The prices come from a local copy of the API with invented numbers. The odds comparison example builds the same idea out with the time each best price was seen.

Step 3: rank prices and add context

Once prices are grouped, the best price is the highest decimal in each row, which SockOdds already puts first. The comparison earns its keep when you show readers what that means.

Worked example: four books, one head to head market

These are invented prices to show the arithmetic, not real odds. A book's margin here is the sum of 1 divided by each price, minus 1.

BookHomeAwaySum of 1/priceMargin
Book A1.852.001.04054.05%
Book B1.901.951.03913.91%
Book C1.881.981.03703.70%
Book D1.832.051.03433.43%
Best of each side1.902.051.01411.41%

The working for the last row: 1/1.90 = 0.5263, 1/2.05 = 0.4878, total 1.0141. No single book has a margin under 3.4%, but the best prices across the market add up to 1.41%. That gap is what a comparison site shows a reader that one bookmaker's app never will.

Every price in SockOdds already carries implied_probability, which is 1 divided by decimal_odds to six places, margin included, so the sum above is one line of code. SockOdds returns each bookmaker's own price exactly as the book showed it. It does not return a typical price or a margin-free price, so if you want a "fair price" column you work it out yourself. Consensus odds covers building a typical price from several books, and how to remove the bookmaker margin covers the maths for stripping the margin.

A better price is not a prediction. It means the reader is paid more if their pick wins; it doesn't make the pick any more likely to win. Say so on the page.

The last click is the one readers came for. SockOdds gives you each bookmaker's key with every price, not a link into its site or bet slip. Keep your own table of outbound links and display names keyed by bookmaker key (the bookmakers list and GET /v1/bookmakers give you every key and name), point each one at the bookmaker's own site, and review it whenever you add a book.

Two rules for links:

  1. Only link to bookmakers licensed in Australia (more on that below).
  2. Don't add a bet-now link to a game that is already in play.

Step 5: refresh without wasting your allowance

Polling faster than prices change buys nothing. SockOdds counts usage in objects: each event in data is one, and so is each bookmaker price inside it, so one game with 20 bookmaker prices uses 21. A board of every market at every bookmaker gets expensive fast.

  • Keep your API key on your server, fetch once per interval, and serve every reader from that cache rather than calling the API per page view.
  • Ask only for what the table shows with markets=, regions= or bookmakers=, and a start window with starts_after and starts_before.
  • Poll more often close to the start and less often days out. Log observed_at to see how often the prices you show actually change.

Limits lists each plan's requests a minute and monthly objects, and odds API rate limits works through a monthly budget.

The Australian rules that shape the site

This section is a starting point, not legal advice. Gambling advertising law differs by state, and a comparison site is a publisher, so get advice before you launch.

List licensed bookmakers only

The Interactive Gambling Act 2001 is enforced against affiliate sites as well as unlicensed offshore bookmakers. In August 2022 the ACMA asked internet providers to block six more sites, including the affiliate sites LegitGamblingSites.com and Gamblers Lab, and by then "some 568 illegal gambling and affiliate websites" had been blocked since its first request in November 2019, iGaming Business reported.

Before a book goes into your table, check it against the ACMA's register of licensed interactive wagering services. The BetStop about page links to the ACMA's list of covered providers. The SockOdds bookmakers page lists the books in the API and regions=au narrows a request to the Australian region, but keeping your own table licensed is your job.

No inducements to open an account

Comparison sites often earn affiliate fees, and bookmakers often supply sign-up offers to promote. In NSW that is where the risk sits. The NSW Government's gambling advertising page says it is an offence to publish or communicate "any inducement to participate, or to participate frequently, in any gambling activity", which "includes an inducement to open a betting account".

Three details matter for a website:

  • A disclaimer that the inducement isn't available to NSW residents is still a breach.
  • The maximum penalty is $110,000 for a corporation and $11,000 for an individual.
  • The offence is in publishing, and the page notes a broadcaster can offend too by continuing to publish an offending advertisement after Liquor & Gaming NSW tells it the publication may breach the law.

The safe default is a site that compares prices and links to bookmakers, with no bonus bets, deposit matches or "join now" offers.

In-play prices are not online bets

Licensed Australian bookmakers can't take sports bets online once play has started. As the ABC reported, "In-play betting is only allowed over the phone". SockOdds is pre-match first and not a low-latency in-play feed, and every event carries a status of scheduled, live, final, postponed or cancelled. Once a game is live, label any prices still on screen as in-play and remove the links that suggest a reader can bet them online.

18+ and BetStop

Build for adults only. Once someone registers with BetStop, the national self-exclusion register, online and phone wagering providers "should not" let them open a betting account, let them place a bet or send them direct marketing, according to the BetStop about page. Link BetStop and a support service from every page that shows prices.

A build checklist

  1. Choose a source that gives every book's market the same id.
  2. Rank prices only within one market id, so line and period match.
  3. Treat a missing price as "no current quote", never as zero.
  4. Show each price's age from observed_at, not the word "live".
  5. Show the margin, or your own typical and fair price, next to the best price.
  6. Link the best price to a licensed Australian bookmaker from your own link table.
  7. Remove online bet links once a game's status is live.
  8. Publish no sign-up inducements, in any state.
  9. Cache on the server and ask only for the markets and books you show.
  10. Carry 18+ and BetStop on every page with prices.

Questions people ask

Publishing odds is not itself banned, but the site has to stay inside the advertising rules. Only feature bookmakers licensed in Australia, never publish offers that induce people to open a betting account, and get legal advice for the states you publish in.

Can an odds comparison site show bonus bet offers?

Be very careful. In NSW it is an offence to publish an inducement to open a betting account, and saying the offer isn't available to NSW residents does not fix it. The maximum penalty is $110,000 for a corporation.

How do I find the best price across bookmakers?

Compare prices within one market id, which already includes the line and the period. In SockOdds each market appears once and each outcome's prices are sorted best first, so the highest decimal_odds for an outcome is its first price. A higher line or total is a different bet, so never rank it against a lower one.

How often should an odds comparison site refresh prices?

No faster than the prices you show actually change. SockOdds does not promise a refresh interval, so log observed_at for a week and set your poll interval from what you see. Polling faster returns the same prices and spends your monthly objects.

Licensed Australian bookmakers can only take in-play sports bets over the phone, so don't present live prices as bets your readers can place online through a link.

Sources

Next

This guide is for developers building data products. It is not betting advice and does not suggest any bet will win. 18+ only. Please gamble responsibly.

SockOdds provides data only. It does not offer tips or predictions on any game.

Search across the SockOdds documentation and catalogue.