Discover archived fixture IDs
using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Get, RequestUri = new Uri("https://oddslink.app/v1/historical/sports/nfl/events?dateFormat=iso&limit=100"), Headers = { { "X-API-Key", "<X-API-Key>" }, },};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "net/http" "io")
func main() {
url := "https://oddslink.app/v1/historical/sports/nfl/events?dateFormat=iso&limit=100"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<X-API-Key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder() .url("https://oddslink.app/v1/historical/sports/nfl/events?dateFormat=iso&limit=100") .get() .addHeader("X-API-Key", "<X-API-Key>") .build();
Response response = client.newCall(request).execute();const url = 'https://oddslink.app/v1/historical/sports/nfl/events?dateFormat=iso&limit=100';const options = {method: 'GET', headers: {'X-API-Key': '<X-API-Key>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://oddslink.app/v1/historical/sports/nfl/events?dateFormat=iso&limit=100' \ --header 'X-API-Key: <X-API-Key>'Discover stored fixture metadata. Empty duplicate kickoffs are excluded when another stored kickoff has recorded prices; entirely empty tracked fixtures remain discoverable. Scale covers stored kickoffs in the last 2 years; Business covers all archives. Date bounds are inclusive stored schedule times, not verified actual kickoff or finish times. If any eligible anchor matches the bounds, the fixture includes every eligible stored choice. Sorted by earliest eligible stored anchor then ID; this ordering never chooses the true kickoff. Continue with next_cursor using the same sport/date filters. Conflicting choices have commence_time null and history_ambiguous true. Use one stored_commence_times value as storedCommenceTime on a price route. Stored kickoffs within 30 minutes of each other are treated as one (earliest wins). Soccer fixtures carry league: {key, title} and tennis fixtures tournament, like current fixtures (null when the archive recorded none); the price and prop history bodies carry it too. Other sports carry neither.
Authorizations
Section titled “Authorizations”Parameters
Section titled “ Parameters ”Path Parameters
Section titled “Path Parameters”A historical sport key: nfl, ncaaf, nba, wnba, mlb, nhl, mma, aggregate soccer or aggregate tennis. nhl and mlb serve the same franchise fixtures as their live routes. A soccer league key such as epl serves fixtures with a recorded league matching that key, including older fixtures. Fixtures without a recorded league are served by the aggregate soccer key only. An event outside the key’s league answers 404 UNKNOWN_EVENT, like an unknown ID. League and tournament query selectors are rejected with 400 BAD_REQUEST. Tournament-specific tennis_* keys are not supported by history. Esports history also accepts cs2, lol, dota2, valorant, from the start of recording; earlier esports prices are not backfilled.
Example
nflQuery Parameters
Section titled “Query Parameters”How commence_time, every last_update and recorded_at, and archive_from are written: ISO-8601 UTC strings with exactly three fractional digits and trailing Z (iso, e.g. 2026-10-11T13:00:00.000Z) or unix seconds (unix). ISO sub-millisecond digits are truncated. Unix price clocks may be fractional; fixture and gameline archive times use whole seconds.
Inclusive earliest stored kickoff, ISO with timezone or Unix seconds.
Inclusive latest stored kickoff, ISO with timezone or Unix seconds.
Opaque next_cursor from the preceding page with the same filters.
Responses
Section titled “ Responses ”Stored fixture metadata; archive presence does not prove completion.
object
object
ID accepted by historical price routes.
Team display name using the same normalization as current odds.
Team display name using the same normalization as current odds.
Soccer fixtures only: the league a fixture belongs to. key is the public sport key when the league has one (epl; pass it as soccer?league= or as the sport key), else a stable key for that league; title is its display name. null when the league is not known for the fixture. Historical bodies carry the same object; a fixture archived before its league was recorded has league: null unless the sport key you asked with names one (epl). Other sports carry no league (tennis and esports carry tournament; every other sport key is one league).
object
Tennis and esports fixtures: the tournament a match belongs to. key is the tournament’s public sport key (tennis_atp_hangzhou; pass it as tennis?tournament= or as the sport key); title is its display name. null when the tournament is not known. Historical bodies carry the same object. For esports, key identifies the tournament within the requested game and is accepted by tournament= on events and odds lists.
object
Stored metadata exists; does not establish actual match completion.
The recorded final score, the same object as the current routes’ score. All sides follow home_team and away_team. Absent when the archive holds no final score.
object
Example
{ "events": [ { "id": "nfl:buffalobills-losangeleschargers:2026-09-27", "sport_key": "nfl", "sport_title": "NFL", "home_team": "Buffalo Bills", "away_team": "Los Angeles Chargers", "commence_time": null, "status": "archived", "history_ambiguous": true, "stored_commence_times": [ "2026-09-27T17:00:00.000Z", "2026-09-27T20:00:00.000Z" ], "score": { "home": 27, "away": 24 } } ], "next_cursor": null, "archive_from": "2026-09-01T00:00:00.000Z"}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
A parameter was malformed.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Example
{ "error": { "code": "BAD_REQUEST", "message": "commenceTimeFrom is not a valid ISO-8601 timestamp.", "docs_url": "https://oddslink.app/docs/errors/#bad-request", "request_id": "req_01hz8k3z9e2v6w1x" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
The X-API-Key header is missing or invalid.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Examples
No X-API-Key header sent
{ "error": { "code": "MISSING_KEY", "message": "X-API-Key header is missing.", "docs_url": "https://oddslink.app/docs/errors/#missing-key", "request_id": "req_01hz8k4a1f3w7x2y" }}Key does not exist or was revoked
{ "error": { "code": "INVALID_KEY", "message": "API key is not valid.", "docs_url": "https://oddslink.app/docs/errors/#invalid-key", "request_id": "req_01hz8k4b2g4x8y3z" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
The key’s plan does not include history (Scale plan and up), or does not reach back to this fixture: Scale covers fixtures that started in the last 2 years, Business covers everything recorded.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Examples
History is not on this plan
{ "error": { "code": "PLAN_REQUIRED", "message": "History requires the Scale plan or above.", "docs_url": "https://oddslink.app/docs/errors/#plan-required", "request_id": "req_01hz8k4c4j6z0a5b", "current_plan": "growth", "required_plan": "scale" }}This fixture is older than the plan's 2-year window
{ "error": { "code": "PLAN_REQUIRED", "message": "The Scale plan covers fixtures that started in the last 2 years. This one started 2024-09-02; the Business plan covers everything recorded.", "docs_url": "https://oddslink.app/docs/errors/#plan-required", "request_id": "req_01hz8k4c5k7a1b6c", "current_plan": "scale", "required_plan": "business" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
The sport key does not exist.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Example
{ "error": { "code": "UNKNOWN_SPORT", "message": "Unknown sport key: basketbal_nba", "docs_url": "https://oddslink.app/docs/errors/#unknown-sport", "request_id": "req_01hz8k4d4j6z0a5b" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
Malformed history filter, cursor, market or stored kickoff selection.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Examples
{ "error": { "code": "INVALID_STORED_COMMENCE_TIME", "message": "Select an exact stored kickoff.", "docs_url": "https://oddslink.app/docs/errors/#invalid-stored-commence-time", "request_id": "req_example" }}{ "error": { "code": "INVALID_CURSOR", "message": "Cursor is invalid or belongs to different filters.", "docs_url": "https://oddslink.app/docs/errors/#invalid-cursor", "request_id": "req_example" }}{ "error": { "code": "INVALID_PARAMETER", "message": "Use an ISO timestamp with timezone or Unix seconds.", "docs_url": "https://oddslink.app/docs/errors/#invalid-parameter", "request_id": "req_example" }}{ "error": { "code": "INVALID_BOOKMAKER", "message": "Unknown bookmaker 'DraftKings'; did you mean draftkings? Valid keys are listed in error.valid_bookmakers.", "valid_bookmakers": [ "1xbet", "4casters", "888sport", "ballybet", "bet105", "bet365", "betano", "betfair", "betmgm", "betonline", "betr", "betrivers", "betus", "betway", "bovada", "bwin", "caesars", "circa", "cloudbet", "draftkings", "fanatics", "fanaticsmarkets", "fanduel", "fliff", "hardrock", "kalshi", "ladbrokes", "lowvig", "matchbook", "novig", "pinnacle", "polymarket", "prizepicks", "prophetx", "rebet", "sbobet", "shuffle", "skybet", "sportbet", "stake", "sxbet", "thescore", "thunderpick", "underdog", "unibet", "williamhill" ], "docs_url": "https://oddslink.app/docs/errors/#invalid-bookmaker", "request_id": "req_example" }}{ "error": { "code": "INVALID_MARKET", "message": "Invalid markets: unknown", "docs_url": "https://oddslink.app/docs/errors/#invalid-market", "request_id": "req_example" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
The plan’s speed limit was exceeded; wait Retry-After seconds. A call that does not count against the allowance is never refused for a spent allowance.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Example
{ "error": { "code": "SPEED_LIMIT", "message": "Speed limit exceeded: 10 requests/second on the Growth plan.", "docs_url": "https://oddslink.app/docs/errors/#speed-limit", "request_id": "req_01hz8k4f7m9c3d8f" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.
Seconds to wait before retrying on 429 SPEED_LIMIT or 503 ODDS_UNAVAILABLE.
No built prices are available yet. Retry after the indicated number of seconds.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Example
{ "error": { "code": "ODDS_UNAVAILABLE", "message": "No built prices are available yet.", "docs_url": "https://oddslink.app/docs/errors/#odds-unavailable", "request_id": "req_example" }}Headers
Section titled “Headers”Seconds to wait before retrying on 429 SPEED_LIMIT or 503 ODDS_UNAVAILABLE.
Something failed on our side.
object
object
Stored Unix-second choices on AMBIGUOUS_EVENT or INVALID_STORED_COMMENCE_TIME; independent of dateFormat.
Valid case-sensitive book keys on INVALID_BOOKMAKER.
403 only: the plan on the presented key.
403 only: the lowest plan that includes this request. For history, that depends on how long ago the fixture started as well as on the endpoint.
Example
{ "error": { "code": "INTERNAL_ERROR", "message": "Something went wrong on our side. It has been logged.", "docs_url": "https://oddslink.app/docs/errors/#internal-error", "request_id": "req_01hz8k4h8n0d4e9f" }}Headers
Section titled “Headers”Requests used this billing period.
Requests remaining this billing period.
What this one call cost against the allowance (0 for a free call).
This plan’s speed limit, in requests per second.
Requests left in the current one-second window.
Seconds until the speed-limit window resets.
This request’s id, for support and for matching against error.request_id.