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)
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 —
401bad key,429rate-limited,5xxretry with backoff. Checkresp.status_codebefore.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.