# 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](https://sockodds.com/docs/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:

```bash
curl -sS "https://api.sockodds.com/v1/results?event_id=ev_000ckk06psy5" \
  -H "Authorization: Bearer $SOCKODDS_API_KEY"
```

```javascript
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));
```

```python
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):

```json
{
  "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:

```javascript
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)}`);
    }
  }
}
```

```python
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):

```text
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](https://sockodds.com/docs/examples/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](https://helpdesk.dabble.com.au/en/articles/9783599-australian-rules-football) (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](https://help.tab.com.au/article/tab-sportsbet-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](https://www.afl.com.au/news/1409464) 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](https://en.wikipedia.org/wiki/List_of_National_Rugby_League_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](https://sockodds.com/blog/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](https://sockodds.com/leagues/) 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](https://sockodds.com/pricing/).

## Sources

- [Dabble: Australian Rules Football betting rules](https://helpdesk.dabble.com.au/en/articles/9783599-australian-rules-football)
- [TAB: TAB Sportsbet (NSW) betting rules](https://help.tab.com.au/article/tab-sportsbet-nsw-betting-rules)
- [AFL: 2025 Toyota AFL Finals Series drawn match (playing of additional time)](https://www.afl.com.au/news/1409464)
- [Wikipedia: List of National Rugby League golden point games](https://en.wikipedia.org/wiki/List_of_National_Rugby_League_golden_point_games)

## Next

- [Results](https://sockodds.com/docs/results/)
- [Settle bets from results](https://sockodds.com/docs/examples/settle-bets/)
- [Bet tracking and settlement](https://sockodds.com/use-cases/bet-tracking-settlement-api/)
- [Rugby union odds: draws and lines](https://sockodds.com/blog/rugby-union-odds-api/)

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