Guides · 7 October 2026 · updated 14 October 2026

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:

FieldWhat it holds
eventThe event, in the same shape as /v1/events/{event_id}.
statusfinal, or corrected when the official source changed a result after it was first published.
settlementcomplete when every market we quoted on the event is settled, partial while any is pending.
pending_marketsHow many quoted markets are still waiting for official evidence.
settled_atWhen the result was last settled or extended, in UTC.
outcomesThe graded outcomes, each with its id, name, side, line, market (including its period) and verdict.

The verdict is one of four:

VerdictWhat your ledger does
wonPays stake × price.
lostPays nothing.
voidReturns the stake.
dead_heatPays 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 open

The 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_1 or half_2. full includes golden point where it is played; regulation is the 80 minutes.
  • AFL markets settle on full, the halves and the quarters. The catalogue has no separate regulation period 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

Next

SockOdds provides data only. It does not offer tips or predictions on any game.

Search across the SockOdds documentation and catalogue.