Krok Odds

13 verified API examples

Every example below was recorded from a live call to the public API and carries machine-checked assertions, so it cannot drift from what the API actually returns.

Verified Examples

Worked examples of behaviour shipped this week, each recorded from a live call. These are not hand-written illustrations: every example carries machine-checked assertions, andnpm run docs:verifyreplays them against the public API and fails the run if any documented behaviour stops being true.

`type` is an alias of `race_type` on the boardrecorded 2026-10-05

On /racing/meetings both `type` and `race_type` filter the code, and they return the identical set of meetings.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/meetings?date=2026-10-05&type=T&limit=5"
{
  "success": true,
  "data": [
    {
      "id": "tab_1234567",
      "venue": "Flemington",
      "state": "VIC",
      "country": "AU",
      "type": "T",
      "date": "2026-10-05",
      "race_count": 8,
      "next_jump": "2026-10-05T04:35:00.000Z",
      "races": [
        {
          "race_key": "2026-10-05_flemington_T_5",
          "number": 5,
          "status": "open"
        }
      ],
      "data_quality": {
        "books_count": 12,
        "has_results": false,
        "last_price_update": "2026-10-05T03:58:12.000Z"
      }
    }
  ],
  "meta": {
    "count": 6,
    "tier": "api",
    "limit": 100,
    "requested_limit": 5
  }
}
  • `type=T|H|G` and `race_type=T|H|G` are interchangeable on this endpoint.
  • Every meeting in the response has `type` set to the requested code.
  • The two spellings return the same number of meetings (asserted against the `race_type` example).
Checked on every run:data[].type eqmeta.count gtemeta.count gte
`race_type` on the board (same result as `type`)recorded 2026-10-05

`race_type` is the spelled-out form of the same filter, and returns the same meetings as `type`.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/meetings?date=2026-10-05&race_type=T&limit=5"
{
  "success": true,
  "data": [
    {
      "id": "tab_1234567",
      "venue": "Flemington",
      "type": "T",
      "date": "2026-10-05",
      "race_count": 8
    }
  ],
  "meta": {
    "count": 6,
    "tier": "api",
    "limit": 100,
    "requested_limit": 5
  }
}
  • The board echoes neither parameter in `meta` — the filter shows up in the data, not in a meta field.
  • Use whichever spelling you prefer; both are supported here.
Checked on every run:data[].type eqmeta.count gte
Default price-line taxonomy: `fixed` + `group` onlyrecorded 2026-10-05

A default request serves only `fixed` and `group` price lines. Every served odds row carries `bookmaker_type`, and group rows also carry the white-label roster in `brands[]`.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/meetings?date=2026-10-05&limit=5"
{
  "data": [
    {
      "races": [
        {
          "runners": [
            {
              "number": 3,
              "name": "Zaaki",
              "best_win": 3.2,
              "best_win_bookmaker": "Sportsbet",
              "odds": [
                {
                  "bookmaker": "Sportsbet",
                  "bookmaker_type": "fixed",
                  "win": 3.2,
                  "place": 1.5
                },
                {
                  "bookmaker": "PuntersTech",
                  "bookmaker_type": "group",
                  "win": 3.25,
                  "brands": [
                    "BetAlpha",
                    "BetBlitz",
                    "MintBet",
                    "PuntersTech",
                    "XBet"
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
  • `bookmaker_type` is one of `fixed | tote | exchange | group | derived | capped`.
  • Default board = `fixed` + `group`; the other four types need `include=` (see the next example).
  • A `group` odds row aggregates a platform’s whole roster and lists it in `brands[]`.
  • The six types are also published by /racing/bookmakers with the same spelling.
Checked on every run:data[].races[].runners[].odds[].bookmaker_type indata[].races[].runners[].odds[].bookmaker_type somegroupsCarryBrands
`include=` opts into the extra price linesrecorded 2026-10-25

`include=` adds the opt-in price-line types to the default `fixed`+`group` set; an unknown type is rejected with a 400.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/meetings?date=2026-10-25&include=exchange&limit=5"
{
  "data": [
    {
      "races": [
        {
          "runners": [
            {
              "number": 3,
              "odds": [
                {
                  "bookmaker": "Sportsbet",
                  "bookmaker_type": "fixed",
                  "win": 3.2,
                  "place": 1.5
                },
                {
                  "bookmaker": "Betfair",
                  "bookmaker_type": "exchange",
                  "win": 3.4,
                  "lay": 3.5,
                  "matched": 42000
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
  • Comma-separate the values. Valid values: `fixed`, `tote`, `exchange`, `group`, `derived`, `capped`.
  • The default board carries `fixed` and `group`; opt in for `exchange` (with `lay`/`matched`) and the rest.
  • A requested type only appears for the races where that price line is published — an opted-in type can be absent for a whole meeting.
  • An unknown value is a 400: `{"success":false,"error":"Unsupported bookmaker type: bogus"}`.
Checked on every run:data[].races[].runners[].odds[].bookmaker_type somedata[].races[].runners[].odds[].bookmaker_type somedata[].races[].runners[].odds[].bookmaker_type in
`bookmaker_type` and `brands[]` on the catalogue

/racing/bookmakers labels every book with the same `bookmaker_type` enum the odds rows use, and group entries list their white-label roster in `brands[]`.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/bookmakers"
{
  "success": true,
  "data": [
    {
      "id": "ladbrokes",
      "name": "Ladbrokes",
      "region": "au",
      "supported": true,
      "is_aggregator": false,
      "is_exchange": false,
      "bookmaker_type": "fixed"
    },
    {
      "id": "punterstech",
      "name": "PuntersTech",
      "region": "au",
      "supported": false,
      "is_aggregator": true,
      "is_exchange": false,
      "bookmaker_type": "group",
      "brands": [
        "BetAlpha",
        "BetBlitz",
        "MintBet",
        "XBet"
      ]
    },
    {
      "id": "tab",
      "name": "TAB",
      "region": "au",
      "supported": true,
      "is_aggregator": false,
      "is_exchange": false,
      "bookmaker_type": "tote"
    },
    {
      "id": "betfair_ex_au",
      "name": "Betfair",
      "region": "au",
      "supported": true,
      "is_aggregator": false,
      "is_exchange": true,
      "bookmaker_type": "exchange"
    }
  ],
  "meta": {
    "count": 23,
    "timestamp": "2026-10-05T01:20:00.000Z"
  }
}
  • Use this catalogue to know up front which labels will appear as `group`, `tote` or `exchange` in an odds payload.
  • `brands[]` is present on `group` entries (e.g. 20+ names for a white-label platform) and omitted elsewhere.
  • `is_aggregator` / `is_exchange` remain, but `bookmaker_type` is the field the odds rows carry.
Checked on every run:data[].bookmaker_type indata[].bookmaker_type notNulldata[].bookmaker_type somegroupsCarryBrands
Results archive: `race_type` is the filterrecorded 2026-09-26

On /racing/results `race_type` is the documented filter and `type` is its alias — the response is filtered either way, and `meta.race_type` echoes the code that was applied.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results?since=2026-09-26&until=2026-09-26&race_type=T&limit=2000"
{
  "success": true,
  "data": [
    {
      "id": "2026-09-26_market-rasen_T_1",
      "race_key_v2": "2026-09-26_market-rasen_T_1",
      "date": "2026-09-26",
      "track": "Market Rasen",
      "race_no": 1,
      "race_type": "T",
      "racing_type": "T",
      "status": "resulted",
      "data_quality": {
        "books": 0,
        "has_result": true
      },
      "winner": {
        "tab_number": 5,
        "name": "Jilord Viva",
        "slug": "jilord-viva"
      },
      "runners": [
        {
          "tab_number": 5,
          "name": "Jilord Viva",
          "finish_position": 1,
          "win_result": "Winner"
        }
      ]
    }
  ],
  "meta": {
    "count": 375,
    "tier": "api",
    "limit": 2000,
    "race_type": "T"
  }
}
  • `race_type=T|H|G` filters the archive and is echoed in `meta.race_type`.
  • Every returned row carries the matching `race_type` (and `racing_type`).
  • `type=` is the same filter under the shorter name — see the next example.
Checked on every run:data[].race_type eqmeta.race_type eqmeta.count gte
Results archive: `type=` is the same filter as `race_type`recorded 2026-10-25

`/racing/results` reads `type` as an alias of `race_type`: the same rows come back and `meta.race_type` echoes the code that was applied. With both present, `race_type` wins.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results?since=2026-10-25&until=2026-10-25&type=T&limit=2000"
{
  "success": true,
  "data": [
    {
      "id": "2026-09-26_market-rasen_T_1",
      "race_type": "T"
    }
  ],
  "meta": {
    "count": 375,
    "tier": "api",
    "limit": 2000,
    "race_type": "T"
  }
}
  • `type=T` returns the same set as `race_type=T` (asserted against the previous example).
  • `meta.race_type` echoes the applied code for either spelling — log it rather than trusting your request string.
  • Both spellings work across the racing routes; `race_type` is the documented one.
Checked on every run:meta.race_type eqdata[].race_type eqmeta.count gte
Dated results: `race_type=T` filters the cardrecorded 2026-10-04

On `/racing/results/{date}` the code filter is `race_type`, echoed in `meta.race_type`, and every returned row is that code.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results/2026-10-04?race_type=T&limit=2000"
{
  "success": true,
  "data": [
    {
      "id": "2026-09-26_market-rasen_T_1",
      "race_key": "2026-09-26_market-rasen_T_1",
      "date": "2026-09-26",
      "track": "Market Rasen",
      "race_no": 1,
      "race_type": "T",
      "status": "resulted",
      "winner": {
        "name": "Jilord Viva",
        "tab_number": 5
      }
    }
  ],
  "meta": {
    "count": 74,
    "date": "2026-10-04",
    "race_type": "T",
    "tier": "api"
  }
}
  • `race_type=T|H|G` filters the day and is echoed in `meta.race_type`.
  • `type=` is an accepted alias here too — see the next example.
  • The dated endpoint is the full-detail card (margins, times, tote pools); /racing/results is the lighter archive.
Checked on every run:data[].race_type eqmeta.race_type eqmeta.count gte
Dated results: `type=` is the same filter as `race_type`recorded 2026-10-25

The dated endpoint reads `type` as an alias of `race_type` and echoes the applied code in `meta.race_type`; both spellings return the same card.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results/2026-10-25?type=T&limit=2000"
{
  "success": true,
  "data": [
    {
      "id": "2026-09-26_market-rasen_T_1",
      "race_type": "T"
    }
  ],
  "meta": {
    "count": 74,
    "date": "2026-10-25",
    "race_type": "T",
    "tier": "api"
  }
}
  • `type=T` returns the same set as `race_type=T` (asserted against the previous example).
  • `meta.race_type` echoes the applied code, so a filter that was not applied is visible in the response.
  • Every racing route accepts both spellings; `race_type` is the documented one.
Checked on every run:meta.race_type eqdata[].race_type eqmeta.count gte
Results archive without a code filterrecorded 2026-09-26

The unfiltered archive for a day range returns the races that settled in it, across every code.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results?since=2026-09-26&until=2026-09-26&limit=2000"
{
  "success": true,
  "data": [
    {
      "id": "2026-09-26_market-rasen_T_1",
      "race_type": "T"
    },
    {
      "id": "2026-09-26_gawler_G_3",
      "race_type": "G"
    },
    {
      "id": "2026-09-26_addington_H_2",
      "race_type": "H"
    }
  ],
  "meta": {
    "count": 645,
    "tier": "api",
    "limit": 2000,
    "race_type": null
  }
}
  • One day of results spans `T`, `H` and `G`.
  • Use `since`/`until` for a day range (paid plan; a free key gets the tier gate).
  • `limit` caps the page and `meta.count` reports how many races matched the window.
Checked on every run:data[].race_type indata[].race_type some
Identity gate: results are named, or reported `pending`recorded 2026-09-26

A result row is only served when the runner can be named; a race with any unidentified result row is reported as `pending` with `winner: null`, and `status` is never null.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results/2026-09-26"
{
  "success": true,
  "data": [
    {
      "id": "2026-09-26_aquis-park-gold-coast_T_1",
      "race_type": "T",
      "status": "pending",
      "winner": null,
      "runners": [
        {
          "tab_number": null,
          "name": null,
          "finish_position": null
        },
        {
          "tab_number": 4,
          "name": "Named Runner",
          "finish_position": null
        }
      ],
      "data_quality": {
        "books": 7,
        "has_result": false
      }
    },
    {
      "id": "2026-09-26_market-rasen_T_1",
      "status": "resulted",
      "winner": {
        "tab_number": 5,
        "name": "Jilord Viva",
        "slug": "jilord-viva"
      },
      "runners": [
        {
          "tab_number": 5,
          "name": "Jilord Viva",
          "finish_position": 1,
          "win_result": "Winner"
        }
      ]
    }
  ],
  "meta": {
    "count": 645,
    "tier": "api",
    "limit": 2000,
    "race_type": null
  }
}
  • Rows carrying a finishing position but no runner name are dropped — you never receive "a result" that names nobody.
  • A race with at least one such row is `pending` with `winner: null` even if a winner is identifiable, because the surviving rows are not a complete finishing order.
  • `status` is always a string: `resulted`, `finished`, `pending`, … — never null.
  • The gate applies to every code on the day, so the example requests the whole day rather than one code.
  • `data_quality.has_result` reflects the served rows only.
Checked on every run:data[].status notNulldata[].status somenoNamelessResultRowspendingRacesHaveNoWinnerstatusNeverNull
GET /me/usage and the X-Credits-* headers

Every metered response carries credit headers; /me/usage returns the same numbers as a body. Four headers appear: X-Credits-Limit, X-Credits-Remaining, X-Credits-Reset and (on metered endpoints only) X-Credits-Cost.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/me/usage"
{
  "success": true,
  "data": {
    "tier": "api",
    "credits_used": 7071,
    "credits_remaining": 42929,
    "credits_limit": 50000,
    "usage_pct": 14.14,
    "reset_date": "2026-11-01T00:00:00.000Z",
    "rate_limit": {
      "limit": 500,
      "remaining": 491,
      "reset": "2026-10-05T01:28:00.000Z"
    }
  },
  "meta": {
    "truncated": false,
    "timestamp": "2026-10-05T01:27:06.755Z"
  }
}
  • `X-Credits-Limit` / `X-Credits-Remaining` / `X-Credits-Reset` are on every authenticated response, including this one.
  • `X-Credits-Cost` is on the endpoint that charges: e.g. `/racing/results/{date}` returns `X-Credits-Cost: 5`.
  • `X-RateLimit-*` is the short-term request window; `X-Credits-*` is the monthly allowance — different limits, both in the headers.
  • `data.usage_pct` is `credits_used / credits_limit × 100`, rounded to 2dp.
Checked on every run:data.tier notNulldata.credits_limit gtedata.credits_remaining gtedata.usage_pct gtedata.usage_pct ltedata.reset_date notNullheader x-credits-limit notNullheader x-credits-remaining notNullheader x-credits-reset notNull
`X-Credits-Cost` on a metered endpointrecorded 2026-09-26

A metered endpoint advertises what it charged in the same header set as the credit balance.

curl -H "X-API-Key: $KROK_API_KEY \
  "https://krokodds.com.au/api/v1/racing/results/2026-09-26?race_type=T&limit=2"
{
  "headers": {
    "x-ratelimit-tier": "api",
    "x-ratelimit-limit": "500",
    "x-credits-cost": "5",
    "x-credits-limit": "50000",
    "x-credits-remaining": "42924",
    "x-credits-reset": "2026-11-01T00:00:00.000Z"
  }
}
  • `X-Credits-Cost` is the per-request charge for this endpoint (5 for the dated results endpoint).
  • `X-Credits-Remaining` is read after the charge is applied.
Checked on every run:header x-credits-cost notNullheader x-credits-cost gteheader x-credits-limit notNullheader x-credits-remaining notNull

Reproduce them: KROK_API_KEY=… npx tsx scripts/api-docs-vs-live.ts