Odds Data API for Sports Betting Models and Backtesting
Features, labels and a closing-price benchmark, joined by shared ids.
A betting model needs features (prices), labels (outcomes) and a benchmark to judge itself against (the closing price). SockOdds v1 provides all three under the same ids, so building a training set is a join rather than a matching project. Price history and opening and closing snapshots are on the Pro plan, and history goes back to 22 December 2025 for some leagues (dates by league).
Why SockOdds
- Prices as features. Every bookmaker's
decimal_oddsandimplied_probabilityper outcome, pluslinefor handicaps and totals. - Movement as features.
GET /odds/historyreturns every recorded change for a game, so opening price, size of move and time before the start are all things you can derive. - Labels graded from official sources.
GET /resultsgives each outcome a verdict ofwon,lost,void,dead_heat,half_wonorhalf_lost. Outcomes the evidence cannot settle are left out, not guessed. - A closing benchmark.
snapshots=closinggives the last price recorded before the start for each quote, the usual yardstick for a model's prices. - The same ids everywhere. Event, market and outcome ids match across odds, history and results, and across bookmakers. The JSON loads into any data frame library.
What you can build
A training set
Page through /events for a date window, pull each game's history and results, and join on outcome id. Treat an outcome with no verdict as unlabelled, never as a loss.
A backtest
Replay your model's picks against the prices recorded at the time, settle them with /results, and report both return and closing line value.
A calibration check
Compare your predicted probabilities with the closing implied_probability once the margin is removed, and with what actually happened.
Try it
This request asks for Sportsbet's closing winner prices on a finished NFL game.
curl -sS "https://api.sockodds.com/v1/events/ev_000ckk0h3n6r/odds?markets=winner&bookmakers=sportsbet&snapshots=closing" \
-H "Authorization: Bearer $SOCKODDS_API_KEY"const response = await fetch("https://api.sockodds.com/v1/events/ev_000ckk0h3n6r/odds?markets=winner&bookmakers=sportsbet&snapshots=closing", {
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_000ckk0h3n6r/odds?markets=winner&bookmakers=sportsbet&snapshots=closing",
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_000ckk0h3n6r",
"name": "Arizona Cardinals v Atlanta Falcons",
"league": {
"key": "nfl",
"name": "NFL"
},
"scheduled_start": "2026-10-12T08:00:00.000Z",
"status": "final",
"participants": [
{
"role": "away",
"identity": {
"key": "atlanta_falcons",
"name": "Atlanta Falcons"
}
},
{
"role": "home",
"identity": {
"key": "arizona_cardinals",
"name": "Arizona Cardinals"
}
}
],
"markets": [
{
"id": "mk_s4fv79z47rbkwev7",
"key": "winner",
"name": "Winner",
"period": "full",
"subject": null,
"alternate": false,
"outcomes": [
{
"id": "oc_53f4yzaqeyex1xhg",
"name": "Arizona Cardinals",
"side": "home",
"line": null,
"prices": [
{
"bookmaker": "sportsbet",
"decimal_odds": null,
"implied_probability": null,
"available": false,
"closing": {
"decimal_odds": 1.72,
"implied_probability": 0.581395,
"observed_at": "2026-10-12T06:00:00.000Z"
},
"observed_at": "2026-10-12T06:00:00.000Z"
}
]
},
{
"id": "oc_1xh21t2rzp7ss665",
"name": "Atlanta Falcons",
"side": "away",
"line": null,
"prices": [
{
"bookmaker": "sportsbet",
"decimal_odds": null,
"implied_probability": null,
"available": false,
"closing": {
"decimal_odds": 2.12,
"implied_probability": 0.471698,
"observed_at": "2026-10-12T06:00:00.000Z"
},
"observed_at": "2026-10-12T06:00:00.000Z"
}
]
}
]
}
]
},
"meta": {
"count": 1,
"cursor": null
},
"links": {
"next": null
}
}Each outcome has available: false and a null current price, because the game is over, and a closing object with the last price recorded before the start. Join these outcome ids to /results?event_id= for the labels, and to /odds/history for the movement that led there.