Public Freshness
Per-bookmaker freshness — last_update summary across all markets.
Returns: bookmakers: [ { key, market_count, latest_update, staleness_seconds, is_stale, worst_staleness_seconds, suspended_market_count, market_classes: { game_lines: { market_count, latest_update, staleness_seconds, active_market_count, oldest_market_update, worst_staleness_seconds, suspended_market_count }, props: { ...same shape }, } } ] stale_threshold_seconds as_of (server time the rows were sampled)
staleness_seconds is the time since the most recent successful market write for the book — a rough proxy for "how fresh is the freshest line on this book." It overstates per-event freshness because any single market touch resets it; for the marketing page that's fine since the public claim we're backing is "we ingest book data continuously and recently."
market_classes splits that per class (game lines vs props) so a class going dark behind fresh game lines — the Pinnacle 2026-08-02 prop incident — is visible here too. Single grouped query, no Event join, so unlike the monitor's query this split does NOT exclude futures markets (they land under props and can keep it looking fresher than the per-game prop board really is) — acceptable for a trust surface.
A third futures class was considered (sportsapis.dev review follow-up, 2026-08-27) and deliberately NOT added, for two reasons that compound: (1) futures is an Event.is_future property, not a Market.key property, so market_class_case() — which buckets on Market.key alone and is SHARED with the monitor — cannot emit it without an Event join in every caller. And the monitor (sport_book_class_freshness_rows) deliberately EXCLUDES futures so a year-round-fresh outright can't mask a dark prop parse, so it can never carry a futures class: "keep the vocabulary shared with the monitor" and "add a futures class" are mutually exclusive on the same shared expression. (2) Splitting futures out of props correctly (props excluding futures) forces an Event join into THIS first query, which is deliberately join-free and spans every market in the DB — not the bounded active-board window the ~167ms cost was measured on. The worst-case fields (worst_staleness_seconds et al.) ALREADY exclude futures via the active-board Event join, so the only residual is a mildly inflated headline props count/latest on a trust page — already noted as acceptable above. Net: high coupling + unbounded cost to fix a cosmetic inflation. Skipped on purpose.
Each class (and the book) also carries the WORST case: worst_staleness_seconds / oldest_market_update — the age of the oldest NON-SUSPENDED market on the active board (events from -6h to +48h) — plus active_market_count and suspended_market_count. Headline staleness answers "is the book being ingested"; worst-case answers "is any individual market being silently missed". A market with suspended_at set is excluded from the worst-case number because a flagged withdrawal is not a capture hole (the book pulled it; we keep its last price and say so on /odds). Second grouped query with an Event join, bounded by the active-board window.
Response
Successful Response
Changes
No changes recorded. 2 of the 26 revisions have no diff computed, so they could not be searched.