Historical odds
A past board, one event's price changes, closing lines and day files: the five calls, how they are priced, the archive's window and the errors.
Before you start
Historical odds hold every bookmaker price change the UK & Ireland raw feed has served since 17 October 2026. They answer at the live feed's base URL, https:/, on a key that includes the Historical odds product. A key without it is refused with 403 not_.
Every route is GET. region is uk or left out; any other region is 404 unknown_. Instants are ISO-8601 to the millisecond, and one without a zone is read as UTC.
Historical odds carry bookmaker prices only. Naming an exchange in exchanges, or sending exchangesOnly=, is 403 exchange_.
On a self-serve plan, history is for your own use: your models, your research, your team. Publishing it, reselling it and pulls bigger than a plan's pool go through an Enterprise contract.
The archive's window
The archive starts on 17 October 2026 and runs to the end of the previous UTC day: each day is added overnight, once it has ended. An instant or a day after the newest one the archive holds, or before its floor, is 400 date_, and the error's earliest and latest give the window you can ask for. An instant with no board recorded at or before it is 404 no_.
The board at a past instant
GET /v2/historical/odds/raw returns the raw board as it stood at date: one page of up to 250 events, the same event objects as /v2/odds/raw, with limit and cursor as there. date and sports are required (400 missing_ without them), and bookmakers, markets and eventIds narrow it. It is served gzip only, so send Accept-Encoding: gzip, as the examples' --compressed does.
meta adds historical: true, timestamp (the board version at or before date), previous_, next_ (the next change, or where a gap in the recording begins; null at the archive's end) and complete, false inside a gap. competition_ is always null in history: the competition register is not recorded.
curl --compressed -s -H "x-api-key: $ODDSRELAY_KEY" \
"https://api.oddsrelay.io/v2/historical/odds/raw?region=uk&sports=soccer&date=2026-10-17T19:40:00Z"e=true"
# {"cost": …, "remaining": …, "rate_card": "…"}One event's price changes
GET /v2/historical/events/{id}/odds returns one event between from (required) and to. to defaults to 24 hours after from, or the newest instant if that is sooner, and a window spans at most 24 hours. An id merged into another event answers as the event it became.
First come the prices at from, then one row for each outcome every time its price moved. A row is timestamp, event_, bookmaker, market, outcome, price and last_. removed: true means the outcome left the board, and index appears only on the second and later outcome sharing a name in one market, counting from 1. meta.gaps lists any gap in the recording inside the window, each with its timestamp and why.
limit (at most 5,000 rows, the default) and cursor page through the rows. A page never splits one version of the board, so it can run a few rows past limit.
curl --compressed -s -H "x-api-key: $ODDSRELAY_KEY" \ "https://api.oddsrelay.io/v2/historical/events/$EVENT_ID/odds?region=uk&from=2026-10-17T13:00:00Z&to=2026-10-17T19:45:00Z"
Closing lines
GET /v2/historical/closing returns the events that kicked off on one UTC day, date as YYYY-MM-DD, for the sports you name (required), nested as event, bookmakers, markets and outcomes. bookmakers and markets narrow it, and limit and cursor page through the events, up to 50 a page, by sport (the file's sport, alphabetically), then kick-off, then id.
Each outcome carries the fields the closing file holds, named as the file names them:
name, andord, which numbers outcomes sharing a name in one market from 0open_andat open_: when the outcome was first seen in the window its file was cut from, and its price thenprice close_andat close_: its last price at or before kick-off, and the instant of its last change at or before kick-off (00:00 UTC, when the file opens, if it did not change that day)price high,lowandmoves: the highest and lowest price before kick-off, and how many times the price movedfirst_andseen last_: the first and last time the outcome was seen on its kick-off day (seen first_is the same instant asseen open_)at inplay_: whether it was still on the board after kick-offseen open_,back_ price open_,lay_ price open_and the same three afteravailable close_: an exchange's back, lay and amount available,nullwhile historical odds carry bookmakers only
curl --compressed -s -H "x-api-key: $ODDSRELAY_KEY" \ "https://api.oddsrelay.io/v2/historical/closing?region=uk&sports=soccer&date=2026-10-17&markets=h2h"
Day files
GET /v2/historical/files lists the files, free. Filter it with tier (closing, 60s and changes, cut every night; 5s and 1s, cut by arrangement and listed only where a file exists), sports, and from and to as UTC days. A file's sport is the sport key's base: soccer, horse_, ice_. A day file holds every bookmaker and market of its sport, so an account whose scope narrows Historical odds, or whose plan limits its bookmakers, is refused on both files routes with 403 files_: contact us to arrange files.
Download a file at its url, GET /v2/historical/files/{tier}/{sport}/{day}.csv.gz. It comes as application/, with an ETag: send it back as If-None-Match and a second download of the same file is a free 304. A day file never changes once cut, so it comes with Cache-Control: private, max-age=86400, immutable; a re-cut changes its ETag. Downloads are sent at a capped rate, one download at a time per account (and only a few across all accounts); one more is refused at once with 429 too_ and Retry-After: wait that many seconds and retry. A download is charged in full when it starts: one you cut off keeps its charge.
A 60s or changes file holds one row per outcome change, sorted by ts, with the columns ts, ts_, event_, sport_, sport_, commence_, home_, away_, bookmaker, bookmaker_, bookmaker_, is_, market, outcome, ord, price, back_, lay_, available, last_ and op. op is image for the outcome as the file opens (each file starts with the whole board at 00:00 UTC, so one day stands alone), set when its price moved or it appeared, and remove when it left the board. In 60s, an outcome has at most one row a minute, as it stood at that minute's end. A closing file holds the closing call's fields, one row per outcome.
# 1. The files for one day: each entry's name, tier, sport, day, size in bytes, url and cost in tokens (free) curl --compressed -s -H "x-api-key: $ODDSRELAY_KEY" \ "https://api.oddsrelay.io/v2/historical/files?region=uk&tier=closing&sports=soccer&from=2026-10-17&to=2026-10-17" # 2. Download one by its url: gzipped CSV, saved as it comes curl --compressed -s -H "x-api-key: $ODDSRELAY_KEY" -o closing_soccer_2026-10-17.csv.gz \ "https://api.oddsrelay.io/v2/historical/files/closing/soccer/2026-10-17.csv.gz"
What history costs
History spends the account's one token pool, the live feed's. A page of the board, event or closing call that holds data costs that call's price per page, and a file costs its price for each started megabyte of its size. Filters narrow what a page holds, not its price. /pricing publishes the four prices from GET /v2/pricing.
Add quote= to any of the four priced calls and it returns {cost, instead, free. Listing the files, 304 replies, replies with nothing in them and every refusal are free too.
Errors
The live feed's errors apply as they do there: 401 for the key, 402 insufficient_, 429 for the rate limit. History adds or leans on these:
- 400
date_: the instant or day is outside the archive;out_ of_ range earliestandlatestgive the window - 400
missing_:param date,sportsorfromis missing - 400
bad_: an unknown or repeated parameter, a malformed instant, a tier that is not one of the list, or a board call without gziprequest - 403
exchange_: an exchange was asked for, and historical odds carry bookmakers onlyhistory_ unavailable - 403
files_: on either files route, your account's scope narrows Historical odds or your plan limits its bookmakers. A day file holds every bookmaker and market of its sport, so contact us to arrange filesneed_ whole_ scope - 403
not_: the key does not include Historical oddsin_ key_ scope - 403
sport_,not_ in_ plan market_ornot_ in_ plan bookmaker_: a value outside your account's scope;not_ in_ plan validlists the ones it allows - 404
no_: no board was recorded at or before the instanthistory - 404
unknown_: no event by that id in the windowevent - 404
not_: no file by that name; list them firstfound - 429
too_: a file download past the download cap; wait the Retry-After secondsmany_ downloads
Every parameter: API reference.