Scores and Odds API: Grading AFL and NRL Bets Against Official Results
Verdicts that share an id with the price you took, and the Australian rules your grader has to respect.
A scores and odds API gives you bookmaker prices before the game and graded results after it, keyed so the two line up without matching events across two vendors. In SockOdds API v1, GET /v1/results returns a verdict for each outcome we quoted on a finished event, graded from official result sources only, and each graded outcome carries the same outcome id as its price. The hard part for Australian sport is not the lookup. It is the rules: AFL draws, extra time that counts for some markets and not others, and NRL golden point that can still end level.
This guide covers both: the fields, then the Australian rules your grader has to handle.
Why prices and results should share ids
When odds come from one source and scores from another, you have to match events between them: different event ids, different team name spellings, different start times after a delay. Every mismatch is a bet that sits ungraded or, worse, gets graded against the wrong game.
In v1 the join is done for you at two levels:
- The event. An event id (
ev_and 12 characters) stays the same from the first time we list the game to its result. If two listings turn out to be one game, the API answers the surviving id. - The outcome. Every outcome in a price response has an id (
oc_and 16 characters), shared by every bookmaker's price listed under it. Results reuse that id. Store it with the bet and you settle by looking it up.
If your own records already hold official ids, GET /v1/events/lookup finds our event from an ESPN event id, an AFL match id for AFLW, or an NRL match id for the Rugby League World Cup. Otherwise find a game with GET /v1/events by league and start window. See identifiers.
What a result carries
Ask GET /v1/results with event_id for one game, or with league and a starts_after and starts_before window for a round, and follow links.next until it is null. On the free Developer plan, league is required. Each result holds:
| Field | What it holds |
|---|---|
event | The event, in the same shape as /v1/events/{event_id}. |
status | final, or corrected when the official source changed a result after it was first published. |
settlement | complete when every market we quoted on the event is settled, partial while any is pending. |
pending_markets | How many quoted markets are still waiting for official evidence. |
settled_at | When the result was last settled or extended, in UTC. |
outcomes | The graded outcomes, each with its id, name, side, line, market (including its period) and verdict. |
The verdict is one of four:
| Verdict | What your ledger does |
|---|---|
won | Pays stake × price. |
lost | Pays nothing. |
void | Returns the stake. |
dead_heat | Pays stake × dead_heat_factor × price; the rest of the stake is lost. |
Here is the result of a finished NFL game in the local test API:
curl -sS "https://api.sockodds.com/v1/results?event_id=ev_000ckk06psy5" \
-H "Authorization: Bearer $SOCKODDS_API_KEY"const response = await fetch("https://api.sockodds.com/v1/results?event_id=ev_000ckk06psy5", {
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/results?event_id=ev_000ckk06psy5",
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": [
{
"event": {
"id": "ev_000ckk06psy5",
"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"
}
}
]
},
"status": "final",
"settlement": "partial",
"pending_markets": 1,
"settled_at": "2026-10-12T12:00:00.000Z",
"outcomes": [
{
"id": "oc_1dppq89hj6hzh2vs",
"name": "Arizona Cardinals",
"verdict": "won",
"market": {
"id": "mk_w3g0d83k64amvfmc",
"key": "winner",
"name": "Winner",
"period": "full",
"subject": null
},
"side": "home",
"line": null
},
{
"id": "oc_9cefdq0gf0ng5wz9",
"name": "Arizona Cardinals",
"verdict": "won",
"market": {
"id": "mk_809y8e9bfgq739jt",
"key": "handicap",
"name": "Handicap",
"period": "full",
"subject": null
},
"side": "home",
"line": -3.5
},
{
"id": "oc_cde9mrxgjh51dkzy",
"name": "Atlanta Falcons",
"verdict": "lost",
"market": {
"id": "mk_w3g0d83k64amvfmc",
"key": "winner",
"name": "Winner",
"period": "full",
"subject": null
},
"side": "away",
"line": null
},
{
"id": "oc_mrajcnv0jg71twnp",
"name": "Atlanta Falcons",
"verdict": "lost",
"market": {
"id": "mk_809y8e9bfgq739jt",
"key": "handicap",
"name": "Handicap",
"period": "full",
"subject": null
},
"side": "away",
"line": 3.5
}
]
}
],
"meta": {
"count": 1,
"cursor": null
},
"links": {
"next": null
}
}Partial results grow, they do not change
Each market settles on its own once official evidence settles every one of its outcomes. A result can be published as partial while, say, player markets wait for detail the official source has not published yet. Later reads of the same event add the newly settled outcomes and pending_markets falls; verdicts already published stay as they are. If an official source corrects a result, status becomes corrected and settled_at moves later, so re-read any event whose status or settled_at changed since you last settled it.
Missing is not lost
Results grade only the outcomes we quoted, so an event we never priced has no result. A tie with no published rule, an unusual finish, or a player whose participation the official source does not confirm stays pending. A missing verdict means not settled yet, never that the bet lost.
A settle loop keyed on outcome ids
This program takes the last Sportsbet price before the start of the finished game (the closing snapshot, a Pro feature), reads the graded result, and joins the two on outcome id to work out what a $10 bet on each outcome returns:
const EVENT = "ev_000ckk06psy5";
const STAKE = 10;
const headers = { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` };
const get = async (path) => (await (await fetch(`https://api.sockodds.com/v1${path}`, { headers })).json()).data;
const odds = await get(`/events/${EVENT}/odds?snapshots=closing&bookmakers=sportsbet&markets=winner,handicap,total_points`);
const results = await get(`/results?event_id=${EVENT}`);
const graded = new Map();
for (const result of results) {
console.log(`${result.event.name}: ${result.status}, ${result.settlement}, ${result.pending_markets} pending`);
for (const outcome of result.outcomes) graded.set(outcome.id, outcome);
}
for (const market of odds.markets) {
for (const o of market.outcomes) {
for (const quote of o.prices) {
if (!quote.closing) continue;
const price = quote.closing.decimal_odds;
const line = o.line === null ? "" : ` ${o.line > 0 ? "+" : ""}${o.line}`;
const label = `${market.name} (${market.period}) ${o.name}${line} at ${price.toFixed(2)}`;
const outcome = graded.get(o.id);
if (!outcome) {
console.log(`${label}: pending, stake still open`);
continue;
}
let returned = 0;
if (outcome.verdict === "won") returned = STAKE * price;
if (outcome.verdict === "void") returned = STAKE;
if (outcome.verdict === "dead_heat") returned = STAKE * outcome.dead_heat_factor * price;
console.log(`${label}: ${outcome.verdict}, $${STAKE} returns $${returned.toFixed(2)}`);
}
}
}import json, os, urllib.request
EVENT = "ev_000ckk06psy5"
STAKE = 10
def get(path):
request = urllib.request.Request(
"https://api.sockodds.com/v1" + path,
headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]},
)
with urllib.request.urlopen(request) as response:
return json.load(response)["data"]
odds = get(f"/events/{EVENT}/odds?snapshots=closing&bookmakers=sportsbet&markets=winner,handicap,total_points")
results = get(f"/results?event_id={EVENT}")
graded = {}
for result in results:
print(f"{result['event']['name']}: {result['status']}, {result['settlement']}, {result['pending_markets']} pending")
for outcome in result["outcomes"]:
graded[outcome["id"]] = outcome
for market in odds["markets"]:
for o in market["outcomes"]:
for quote in o["prices"]:
if not quote.get("closing"):
continue
price = quote["closing"]["decimal_odds"]
line = "" if o["line"] is None else " " + ("+" if o["line"] > 0 else "") + str(o["line"])
label = f"{market['name']} ({market['period']}) {o['name']}{line} at {price:.2f}"
outcome = graded.get(o["id"])
if outcome is None:
print(f"{label}: pending, stake still open")
continue
returned = 0
if outcome["verdict"] == "won":
returned = STAKE * price
if outcome["verdict"] == "void":
returned = STAKE
if outcome["verdict"] == "dead_heat":
returned = STAKE * outcome["dead_heat_factor"] * price
print(f"{label}: {outcome['verdict']}, ${STAKE} returns ${returned:.2f}")Output from a local copy of the API with invented test prices
Arizona Cardinals v Atlanta Falcons: final, partial, 1 pending
Winner (full) Arizona Cardinals at 1.72: won, $10 returns $17.20
Winner (full) Atlanta Falcons at 2.12: lost, $10 returns $0.00
Handicap (full) Arizona Cardinals -3.5 at 1.90: won, $10 returns $19.00
Handicap (full) Atlanta Falcons +3.5 at 1.90: lost, $10 returns $0.00
Total points (full) Over +45.5 at 1.87: pending, stake still open
Total points (full) Under +45.5 at 1.93: pending, stake still openThe game and its prices come from a local copy of the API with invented numbers. AFL and NRL have no games in that copy, so the sample uses an NFL game; the loop is the same for any league. On Rookie and Developer, store the price you took with the bet instead of reading the closing snapshot. Settle bets builds the same idea into a ledger.
Periods: full against regulation
Every market has a period, and results grade each outcome on its own market's period. full includes extra time where the sport's rules do; regulation does not. The two are different markets with different ids, so a bet struck on one never settles against the other.
- NRL markets settle on
full,regulation,half_1orhalf_2.fullincludes golden point where it is played;regulationis the 80 minutes. - AFL markets settle on
full, the halves and the quarters. The catalogue has no separateregulationperiod for the AFL.
Check period on every market before you compare two prices or settle a bet.
The Australian rules your grader has to handle
Generic settlement guides often skip these, and they are where AFL and NRL graders go wrong. Each rule below is quoted from its source; bookmakers differ, so store which rules each bet was struck under.
AFL draws are a dead heat, not a push
AFL head to head markets usually have no draw option, but home and away games can end level. Dabble's Australian Rules Football rules (rule 3.3.1) say that when a regular season or pre-season match is drawn and no draw market is offered, "dead heat rules will apply meaning bets will be paid out at half the original stake at the price taken". TAB's NSW betting rules give the general method in rule 6.1.8: divide the payout by the number of competitors in the tie, then multiply by the number of placings they fill.
SockOdds results apply the same rule: a drawn AFL or AFLW head to head is a dead_heat with a dead_heat_factor of 0.5. So a $10 head to head bet at 4.20 on a drawn AFL game returns $21.00, not $10 back and not $42. If your grader only knows win, lose and push, it will settle this wrong.
Extra time counts for some markets and not others
AFL finals don't stay drawn. For the 2025 finals series, the AFL confirmed that additional time of two three-minute halves (plus time on) is played as required until a result is determined. Bookmakers don't treat every market the same way when that happens. Dabble's AFL rules count extra time for the match winner (rule 3.6.1) but state that "Extra time does not count for total match points and team total points" (rule 3.9.2), and its rule 3.3.2 says half, quarter and winning margin markets exclude extra time too.
Because full includes extra time where the sport's rules do, a finals total struck under a rule like Dabble's can settle differently from a full verdict. It is rare, but store the bookmaker's rule with the bet and check it on any finals game that went to extra time.
NRL golden point can still end level
Since the 2003 season the NRL has used golden point: if scores are level after 80 minutes, five minutes are played, the teams swap ends and a further five minutes are played, and the first score wins. If no further scoring occurs, "the game is drawn", per Wikipedia's list of NRL golden point games.
In v1 that is the line between full and regulation: a head to head on full includes golden point, and a three-way result quoted on regulation settles on the 80 minutes. The results docs name a Rugby League World Cup head to head still level after golden point as a dead heat with a factor of 0.5. For a tie with no published rule, the outcome stays pending rather than guessed, so your grader needs a pending branch as well as a dead heat branch.
Player markets depend on taking the field
Player markets have their own void rules. Dabble's AFL rules void bets on a player who is not in the starting 22 (rule 3.14.3), and treat a selection as active once the player takes to the field at any point (rule 3.29.1). SockOdds results never guess participation: a player whose participation the official source does not confirm stays pending. Our AFL odds API guide shows how player markets and player ids work.
What the docs don't promise
Results come only from official result sources, never from a bookmaker's settlement. The docs publish no timing for when a result appears after the final siren, so don't assume one: poll /v1/results for recent events and let settlement, pending_markets and status decide when a bet is done. Results exist only for events we priced, so AFL and NRL results depend on bookmakers delivering those leagues; the leagues page shows who delivers each one.
Questions people ask
Does the SockOdds API include scores and results?
It includes graded results. GET /v1/results returns a verdict (won, lost, void or dead_heat) for each outcome we quoted on a finished event, graded from official result sources, with the same outcome id as the price.
How do I grade an AFL head to head bet that ends in a draw?
Bookmakers that offer no draw option apply dead heat rules, so the bet returns half the stake at the price taken. SockOdds returns a drawn AFL or AFLW head to head as dead_heat with a dead_heat_factor of 0.5.
Does extra time count when grading AFL and NRL bets?
It depends on the market and the bookmaker. In v1, full includes extra time where the sport's rules do and regulation excludes it; the NRL has both periods. One Australian bookmaker's AFL rules include extra time for the match winner but exclude it from totals, so store the rule set with each bet.
What does a partial result mean?
Some markets on the event are still waiting for official evidence. pending_markets counts them. Later reads add the newly settled outcomes without changing verdicts already published.
Is there a results feed on the free plan?
Yes. The Developer plan can read /v1/results for its leagues, with league required on each request. See pricing.
Sources
- Dabble: Australian Rules Football betting rules
- TAB: TAB Sportsbet (NSW) betting rules
- AFL: 2025 Toyota AFL Finals Series drawn match (playing of additional time)
- Wikipedia: List of National Rugby League golden point games
Next
SockOdds provides data only. It does not offer tips or predictions on any game.