Odds API for Arbitrage Betting
Every bookmaker's price for one market in one response, ready for an arbitrage scan.
An arbitrage scanner looks for a market where the best price for each outcome, taken from different bookmakers, implies a total probability under 100%. The maths is short. The hard part is having every bookmaker's price for the same market, recorded close together, in one place. SockOdds v1 returns that in one request, with shared outcome ids and an observed time on every quote.
Prices move. An arbitrage holds only if every leg is accepted at the quoted price. Prices change between the quote and your bet, bets can be voided, and bookmakers may limit accounts that bet this way. Treat a scan result as a lead to check on the bookmaker's own site, never as a sure thing.
Why SockOdds
- Every bookmaker in one response.
GET /events/{event_id}/oddsreturns every bookmaker your plan includes for a game.GET /odds?league=covers a league's games; followlinks.nextfor the next page. - Outcomes already lined up. An outcome has the same
oc_id at every bookmaker, and handicap and total outcomes carry theirline, so you always compare like with like. - Exchanges and prediction markets alongside. Keys such as
betfair_exchange,kalshiandpolymarketcome back in the same list, priced in decimal.GET /bookmakersgives each one'skind. - Freshness you can check. Every quote has
observed_at, and quotes older than 10 minutes are withheld, so a long-gone price cannot create a false arbitrage. - Alternate lines on request. Add
alternates=trueto include alternate handicap and total lines, which is where middles show up.
What you can build
An arbitrage scanner
For each market, take the highest decimal_odds per outcome across bookmakers and add up 1 / decimal_odds. A sum under 1 means the best prices imply less than 100% in total. The margin is 1 / sum - 1, and each outcome's share of the total stake is (1 / decimal_odds) / sum.
A middle finder
With alternates=true, compare handicap lines across bookmakers. When one bookmaker's home line and another's away line leave a gap between them, a final margin inside the gap wins both bets.
A freshness filter
Before you alert anyone, compare the observed_at times of every leg and drop any combination whose quotes were seen too far apart for your taste.
Try it
This request asks for the handicap market on the Rugby League World Cup opener from Ladbrokes and TAB.
curl -sS "https://api.sockodds.com/v1/events/ev_000ckk0zrx33/odds?markets=handicap&bookmakers=ladbrokes,tab" \
-H "Authorization: Bearer $SOCKODDS_API_KEY"const response = await fetch("https://api.sockodds.com/v1/events/ev_000ckk0zrx33/odds?markets=handicap&bookmakers=ladbrokes,tab", {
headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
console.log(JSON.stringify(await response.json(), null, 2));import json, os, urllib.request
request = urllib.request.Request(
"https://api.sockodds.com/v1/events/ev_000ckk0zrx33/odds?markets=handicap&bookmakers=ladbrokes,tab",
headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
print(json.dumps(json.load(response), indent=2))Response from a local copy of the API with invented test prices
{
"data": {
"id": "ev_000ckk0zrx33",
"name": "Australia v New Zealand",
"league": {
"key": "rlwc",
"name": "Rugby League World Cup"
},
"scheduled_start": "2026-10-15T09:05:00.000Z",
"status": "scheduled",
"participants": [
{
"role": "away",
"identity": {
"key": "new_zealand_rugby_league",
"name": "New Zealand"
}
},
{
"role": "home",
"identity": {
"key": "australia_rugby_league",
"name": "Australia"
}
}
],
"markets": [
{
"id": "mk_5j02m4w6004weeze",
"key": "handicap",
"name": "Handicap",
"period": "full",
"subject": null,
"alternate": false,
"outcomes": [
{
"id": "oc_j6paqe6cbz2fy0f8",
"name": "Australia",
"side": "home",
"line": -6.5,
"prices": [
{
"bookmaker": "tab",
"decimal_odds": 1.96,
"implied_probability": 0.510204,
"available": true,
"observed_at": "2026-10-15T07:58:00.000Z"
},
{
"bookmaker": "ladbrokes",
"decimal_odds": 1.86,
"implied_probability": 0.537634,
"available": true,
"observed_at": "2026-10-15T07:58:00.000Z"
}
]
},
{
"id": "oc_74ypb0kmrb0cbefw",
"name": "New Zealand",
"side": "away",
"line": 6.5,
"prices": [
{
"bookmaker": "tab",
"decimal_odds": 1.96,
"implied_probability": 0.510204,
"available": true,
"observed_at": "2026-10-15T07:58:00.000Z"
},
{
"bookmaker": "ladbrokes",
"decimal_odds": 1.86,
"implied_probability": 0.537634,
"available": true,
"observed_at": "2026-10-15T07:58:00.000Z"
}
]
}
]
}
]
},
"meta": {
"count": 1,
"cursor": null
},
"links": {
"next": null
}
}Look at line first: both bookmakers have Australia at -6.5 and New Zealand at +6.5, so the prices compare directly. Then take the best price for each side. Here TAB has the best price on both sides, 1.96, and 1 / 1.96 + 1 / 1.96 is about 1.02, more than 1, so there is no arbitrage. Most markets look like this most of the time. The arbitrage example runs the scan across a whole league.