Freshness fields
The times the data carries, what each one means and how to compute a price's age.
Also as Markdown.
The board time
meta.processed_at, and the X-Processed-At header, are the board's time. X-OddsRelay-Version is the publish instant of the board version served, in epoch milliseconds, the same on every route for one publish.
Each venue's last read
On the matched boards, meta.last_seen is {bookmaker key: {sport group: ISO time}}: the last instant each venue and sport was read, the oldest observation behind the board. Now minus last_seen is an upper bound on that venue's price age. It is keyed by the sport's key base (soccer, horse_racing, american_football), not an event's full sport_key: take the group /v2/sports gives each sport key (American Football) and write it lowercase with underscores. A filtered reply's table is cut to the venues and sports it returns.
Keyed /v2/coverage carries the same last_seen per venue and sport, with event counts.
// groupOf: {sport_key: key base}, built once from GET /v2/sports rows: base = group.toLowerCase().replace(/[^a-z0-9]+/g, "_")
const seen = meta.last_seen[offer.bookmaker]?.[groupOf[event.sport_key]];
const ageSeconds = seen ? (Date.now() - Date.parse(seen)) / 1000 : null;Raw's last_update
On raw, each market's last_update is when that venue's market last changed: a price, a lay price or available moved, or a row appeared. A service restart can move it once with nothing changed, so dedupe on content. It is not a heartbeat: a quiet market's last_update ages while the venue is still read.
What is never cut
The API drops no row for age. A venue whose latest read is older than its own window, at most two minutes, is left off the boards until it is read again.
Every statement here follows the published contract. The API reference has the full detail.