---
title: "Returns historical closing odds per sportsbook: moneyline, spread and total at close for archived games, with the provenance of each row."
method: GET
path: "/api/v1/history/closing-odds"
tags: ["Historical data"]
---

# Returns historical closing odds per sportsbook: moneyline, spread and total at close for archived games, with the provenance of each row.

`GET /api/v1/history/closing-odds`

## Request

- Base URL: `https://api.owlsinsight.com`
- URL: `https://api.owlsinsight.com/api/v1/history/closing-odds`
- Auth: HTTP bearer

Returns historical closing odds per sportsbook: moneyline, spread and total at close for archived games, with the provenance of each row.

Either eventId or sport is required, and sport alone is rejected as too broad: add startDate, book, source or season.

Rows from different sources coexist for the same event and book and are not interchangeable: published-close is the book's final published closing price, live-derived is the last snapshot captured at or before the start time (only as close to the close as capture ran) and exists only for games with a known start time from 2026-09-16, and archive-1, archive-2 and archive-3 are third-party historical archives kept distinct because several can coexist for one event and book.

Three-way moneylines carry a draw key; two-outcome sports omit the key entirely rather than sending null.

Pass the event's eventId field, the {sport}:{away}@{home}-{YYYYMMDD} form. The numeric id field beside it is the sportsbook's own and returns 400 here rather than an empty result. The date in eventId is the UTC start date, except for MLB games starting from 2026-10-11T10:00Z, which carry the US Eastern date; earlier MLB games keep the UTC date. Before that change, an MLB game starting at 8 PM ET or later and the next day's game between the same teams could share one eventId. NBA and NHL stay UTC-dated. NBA has about one pair of games a season sharing an eventId. NHL has about one pair a season of the same teams on one date, home and away swapped: two eventIds, but the /api/v1/history endpoints match both orderings, so each id returns both games' rows.

Historical endpoints share a per-key in-flight limit that is lower than the general concurrent-request limit. A request beyond it returns 429 with a Retry-After header giving the delay in seconds; wait that long and resend.

503 with a Retry-After header means the archive could not serve that request: the query was too expensive to run in one request, or the archive read path was busy. Wait the indicated seconds before resending; if it repeats, narrow the request with the filters this endpoint accepts.

Page one event at a time with limit and offset and stop when a page comes back shorter than limit. Do not fan out one request per book or market in parallel.

## Query parameters

- `eventId` string
- `sport` string
- `book` string
- `source` string
- `startDate` string
- `endDate` string
- `season` string
- `limit` string
- `offset` string

## Response `200`

OK

- ClosingOddsResponse
  - `data` object
    - `dataQuality` ClosingOddsDataQuality: Per-response data quality for /history/closing-odds. This endpoint never withholds rows. A price that cannot be a valid American odd (|price| < 100, which indicates a decimal coefficient stored in an integer column) is omitted per FIELD, leaving the row and its other markets intact.
      - `note` string
      - `rowsExcluded` number, double: Always 0 — rows are never excluded by this endpoint.
      - `suppressedFields` number, double: Count of individual price fields nulled across this response.
    - `odds` ClosingOddsRow[]
      - `awayTeam` string
      - `book` string
      - `eventId` string
      - `gameDate` string
      - `homeTeam` string
      - `lastUpdate` number, double: Epoch SECONDS from the source. The column is BIGINT UNSIGNED, so this is a number.
      - `moneyline` object: `draw` is present ONLY for three-way sports (soccer). Two-outcome sports omit the key entirely rather than sending `draw: null`, so `Object.keys(moneyline)` is safe and an MLB row never advertises an outcome that does not exist. For soccer, computing implied probability from home+away alone will NOT reconcile — you must include the draw.
        - `away` number, double, nullable
        - `draw` number, double, nullable
        - `home` number, double, nullable
      - `source` string: Where a closing-odds row came from. These are NOT interchangeable: - `football-data` the bookmaker's FINAL published closing price - `live-derived` the last snapshot our own capture saw, which is only as close to the close as capture ran that day - `yahoo`/`oddsshark`/`espn` the original third-party backfill (MLB + NBA) For closing-line-value work prefer `football-data` where present.
      - `sport` string
      - `spread` object: Every field null means the spread was captured but withheld: on a live-derived close, a spread whose two points do not cancel or one known to be wrong for that book and date (counted in `dataQuality.suppressedFields`). Absent means none was captured.
        - `away` number, double, nullable
        - `awayPrice` number, double, nullable
        - `home` number, double, nullable
        - `homePrice` number, double, nullable
      - `total` object
        - `line` number, double, nullable: The totals line. Null only when the whole total was withheld (a live-derived close known to be wrong for that book and date); the prices are then null too. The API omits `total` when none was captured.
        - `overPrice` number, double, nullable
        - `underPrice` number, double, nullable
    - `pagination` object
      - `hasMore` boolean
      - `limit` number, double
      - `offset` number, double
      - `total` number, double
  - `success` boolean

---

[API](https://skmtc.dev/owlsinsight/apis/owls-insight-api.md) · [All operations](https://skmtc.dev/owlsinsight/apis/owls-insight-api/llms.txt) · [OpenAPI document](https://skmtc.dev/owlsinsight/apis/owls-insight-api/revisions/76c1ea016ff1?raw)
