18+ Only  |  Gambling can be addictive — please gamble responsibly  |  Gambling Help: 1800 858 858  |  GambleAware
Developer guide

Australian Sports Odds API in Python: The Complete Guide

Pull live odds from Australian bookmakers — 14 on racing, 6 on sports — compare the best price on every selection, measure the line-shopping edge, and add racing — in about 30 lines of Python. Every snippet is copy-paste and runs against a free API key.

Level: BeginnerTime: ~15 minStack: Python 3.8+, requests

The PuntersEdge API returns live odds from 14 Australian bookmakers on racing — BetRight, BetDeluxe, BetGold, BoostBet, TAB, Betr, PointsBet, TABtouch, Sportsbet, Palmerbet, Unibet, NextBet (formerly PlayUp), Ladbrokes and Neds — and from 6 of those on sports, across AFL, NRL, NBA, WNBA, tennis, cricket and racing, as clean REST/JSON. In this guide you'll go from zero to a working multi-bookmaker odds comparison.

1. Get a free API key

Create a key at the API platform — the free tier gives you 3,000 credits a month with no credit card. Every request authenticates with an X-API-Key header. Keep your key out of source control (use an environment variable in real projects).

2. Your first request: list the sports

Install the one dependency and list the available sports. The key field is what you'll pass to the odds endpoints.

# pip install requests
import requests

API = "https://api.puntersedge.online/v1"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}

sports = requests.get(f"{API}/sports", headers=HEADERS).json()
for s in sports:
    print(s["key"], "—", s["title"])

# nrl — NRL
# afl — AFL
# nba — NBA  ...

3. Pull live odds for a sport

The /best-odds/{sport} endpoint returns each upcoming event with the best available price per selection already computed across all books — so you don't have to merge bookmaker feeds yourself. It returns a list of events; each event has a selections list, and each selection carries name, best_price and best_bookmaker.

events = requests.get(f"{API}/best-odds/nrl", headers=HEADERS).json()

for ev in events:
    print(f"\n{ev['home_team']} v {ev['away_team']}")
    for sel in ev["selections"]:
        print(f"  {sel['name']:24} {sel['best_price']:>6}  ({sel['best_bookmaker']})")

# Penrith Panthers v Brisbane Broncos
#   Penrith Panthers          1.65  (pointsbetau)
#   Brisbane Broncos          2.30  (sportsbet)
Tip: swap nrl for any sport key from step 2 (afl, nba, wnba, tennis_atp, soccer_epl… — GET /v1/sports lists every live key, and a key that is not in it 404s). Want to see a live response before you write a line of code? Open the interactive playground.

4. Surface the standout prices on the board

"Line shopping" — comparing the best available price for each selection — is a core concept when you work with odds data. Because the API returns best_price and best_bookmaker per selection, ranking the standout prices on the board is just a sort. (This is a data exercise — it makes no claim about outcomes or returns.)

def best_book_board(sport):
    events = requests.get(f"{API}/best-odds/{sport}", headers=HEADERS).json()
    rows = []
    for ev in events:
        for sel in ev["selections"]:
            rows.append((sel["best_price"], sel["name"], sel["best_bookmaker"]))
    # biggest prices first — the standout value on the board
    rows.sort(reverse=True)
    return rows[:10]

for price, name, book in best_book_board("nrl"):
    print(f"{price:>6}  {name:24} {book}")

5. Add racing

Racing is the highest-volume market in Australia. The /racing/next-to-go endpoint returns the next races to jump with their runners and live win prices per bookmaker — ideal for a next-to-go board or a price tracker.

# next-to-go returns a JSON ARRAY of races — not an object with a "races" key.
races = requests.get(f"{API}/racing/next-to-go", headers=HEADERS).json()

for race in races:
    print(f"\n{race['venue']} R{race['race_number']} ({race['category']})")
    for r in race["runners"][:4]:
        best = max(r["bookmakers"], key=lambda b: b["win_price"])
        # `number` can be null, so don't format it directly
        num = r["number"] if r["number"] is not None else "-"
        print(f"  {num:>2} {r['name']:22} {best['win_price']:>6} ({best['key']})")

Next-to-go gives you one race at a time. To ask the other racing question — is the whole field beatable across books? — use /racing/best-odds, which returns the best price per runner and a market_percentage for the race.

# market_percentage is the overround across the BEST price for each runner.
# Under 100 means the field is beatable book-vs-book. No exchange needed.
races = requests.get(f"{API}/racing/best-odds",
                     headers=HEADERS, params={"categories": "horse"}).json()

for race in races:
    if float(race["market_percentage"]) < 100:
        print(f"{race['venue']} R{race['race_number']} — {race['market_percentage']}%")
        for r in race["runners"]:
            w = r["best_win"]
            print(f"  {r['name']:22} {w['price']:>6} ({w['bookmaker']})")

Two things worth knowing before you build on it. The market_percentage is computed over the live runners only — scratchings are already excluded, so you should never back a runner that has been scratched. And back every runner or you have not hedged anything: covering all but one is a bet that the one you skipped loses, and it will look like a large edge precisely because the missing runner contributes nothing to the total.

Cross-book racing arbs are rarer than sports — most races are comfortably over-round across books, so a scan that finds nothing is the ordinary result rather than a sign something is broken.

6. That's the whole integration

That's it — a few requests calls and one header. The same normalise-then-compare pattern works against any odds source, so nothing here locks you in. Prefer a client library? The official open-source Python SDK (MIT) wraps the core endpoints with typed methods and raises clean exceptions (AuthenticationError, RateLimitError, NotFoundError, ServerError) instead of returning error payloads. Building in Node instead? See the JavaScript & Node.js guide.

pip install puntersedge

from puntersedge import PuntersEdge

pe = PuntersEdge()                 # reads $PUNTERSEDGE_API_KEY, or ~/.config/puntersedge/config
races = pe.racing_next_to_go(categories="horse")
odds  = pe.odds("nrl", markets="h2h")
board = pe.racing_best_odds(categories="horse")   # market_percentage per race

The package also ships a scanner that does the field comparison above for you, applies your own filters and sizes the stakes. Racing is one flag:

puntersedge-arb scan --racing --no-sports --categories horse,greyhound

It never holds a bookmaker login and never places a bet — it finds and sizes, you place. There is no racing back/lay support, because that needs an exchange lay price the API does not serve.

7. Production tips

  • Auth & secrets: read the key from an env var (os.environ["PE_API_KEY"]), never commit it.
  • Errors: the API uses standard codes — 401 bad key, 429 rate-limited, 5xx retry with backoff. Check resp.status_code before .json().
  • Caching: odds change fast but not every second — cache responses for 10–30s to stay well inside your quota.
  • Free tier: 3,000 credits/month is plenty for prototyping; upgrade when you ship.

Next steps

You now have live, multi-bookmaker Australian odds in a few lines of Python — the data layer behind comparison tools, dashboards, alerts and models. From here, explore the full endpoint set or browse coverage by bookmaker and sport.

PuntersEdge provides odds data for developers. 18+. Odds for information only — gamble responsibly.

This site contains wagering-related analysis and is intended for Australian users aged 18+. Gambling involves risk. Please gamble responsibly.