Use case: arbitrage
Arbitrage betting API for Australian bookmakers
An arbitrage appears when the best price on each outcome, taken from different bookmakers, adds up to less than 100% implied. Spotting one means seeing every book's price on the same market at the same moment, which is exactly the shape of one event response.
The maths
When two prices beat the book
Say a tennis match has the first player at 2.08 with one bookmaker and the second at 2.02 with another. Convert each to an implied chance:
1/2.08 + 1/2.02 = 48.08% + 49.50% = 97.58%
Under 100%, so backing both in the right proportions returns the same amount whoever wins: here 2.48% on the total staked. Split $1,000 as $492.68 and $507.32, and either result pays $1,024.78.
stakeᵢ = total × (1/oddsᵢ) ÷ Σ(1/odds)
What the feed gives you
Everything a scanner needs, per market
- Every book in one response. Each bookmaker's outcomes carry a
decimal_oddsprice, and an outcome has the same ID at every book, so the best price per side is one pass. - Availability and age.
availabledrops suspended quotes;observed_attells you how old each one is. - Alternate lines with
alternates=true, for middles and for pairing totals at matching lines. - Shared boards named. The bookmakers page notes the brands that carry another book's prices, so a scanner does not count one price twice.
- Prediction markets in the same response. Kalshi and Polymarket sit beside the books for cross-venue checks.
Code
A head to head arbitrage scanner in Python
This walks every MLB game, takes the best available whole-game price on each side and prints any pair under 100%. Baseball has no draws, which keeps a two-way head to head honest.
import os
import requests
resp = requests.get(
"https://api.sockodds.com/v1/odds",
params={"league": "mlb", "markets": "winner"},
headers={"Authorization": f"Bearer {os.environ['SOCKODDS_KEY']}"},
timeout=20,
)
resp.raise_for_status()
for event in resp.json()["data"]:
# Best available price per side. An outcome has the same ID at every
# book, so the books line up without any name matching.
best = {} # side -> (decimal price, bookmaker)
for book in event["bookmakers"]:
for market in book["markets"]:
if market["key"] != "winner" or market["period"] != "full": # whole-game head to head
continue
for o in market["outcomes"]:
if o.get("available") and o["decimal_odds"] > best.get(o["side"], (0, ""))[0]:
best[o["side"]] = (o["decimal_odds"], book["key"])
if "home" not in best or "away" not in best:
continue
(home, home_book), (away, away_book) = best["home"], best["away"]
total = 1 / home + 1 / away
if total < 1:
print(f'{event["name"]}: {home} at {home_book}, '
f'{away} at {away_book}, {1 / total - 1:.2%} return')
Middles
When the lines disagree
A middle is the cousin of an arb. If one book has an NRL total at 40.5 and another at 44.5, the over at 40.5 and the under at 44.5 both win if the game lands on 41 to 44 points. Ask for alternates=true and every line each book offers comes back as its own market, with the number on each outcome's line and alternate: true on the ones away from the main line.
For lines and totals, always compare sides at the same number, because a better price at a worse line is not an arb. Grouping by outcome ID does this for you: a total at a different number is a different market, with different IDs.
The traps, said plainly.
- Prices are re-read about every five minutes, so an arb on screen can be gone at the bookmaker. Check the price there before acting.
- Draws break two-way maths. In the AFL a drawn head to head is usually settled as a dead heat, and soccer's 1X2 has three outcomes.
- Two brands on one shared price board are one opinion, not two.
- Bookmakers can limit accounts that bet this way and can void bets placed on obvious pricing errors. Read each book's terms.
Fast Odds API supplies data only. Whether and how you bet is your call.
Which plan suits an arbitrage scanner?
Pro, in practice. Arbs tend to sit with the smaller books whose prices lag the market, and Pro is the plan that includes every bookmaker with no monthly object cap. Build on the free plan first; the code does not change.
How often should a scanner poll?
About every five minutes. The source refreshes about every five minutes, so polling faster costs quota without showing you anything new. Compare each outcome's observed_at with your last copy to skip what has not changed.
Can I scan player props for arbs?
Yes. Props come back in the same event response with every book's line and price. Pair over and under at the same line, because books often hang different numbers on the same player.
Does the API place the bets?
No. It is a read-only data feed. A person places any bet at the bookmaker, under that bookmaker's rules.
Start now
Your first Australian price is one request away.
Free key, no card, AFL and NRL included. Keys are issued on sockodds.com.