---
title: "Returns historical closing player prop lines per sportsbook from the backfill archive: the line and over/under prices at close for each player and prop type."
method: GET
path: "/api/v1/history/player-props"
tags: ["Historical data"]
---

# Returns historical closing player prop lines per sportsbook from the backfill archive: the line and over/under prices at close for each player and prop type.

`GET /api/v1/history/player-props`

## Request

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

Returns historical closing player prop lines per sportsbook from the backfill archive: the line and over/under prices at close for each player and prop type.

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.

At least one of eventId, player or startDate is required; sport alone is rejected as too broad.

A player search that matches an unusually large number of distinct names is treated as a scan and served through a slower fallback with a tighter timeout.

The line and the prices live under `closing`, not at the top level of the row, and the player field is `playerName`. `opening` is present only on rows where an opening line was also captured, so treat it as optional.

A row carries EITHER a labelled pair or a single unlabelled price. `overPrice` and `underPrice` are set when the source stated which side each price belongs to. `americanPrice` is set when it published one price without naming a side. So null side prices mean the row was published one-way, NOT that the price is missing: on one NBA event of 1,133 rows, every row carried a price and 1,057 of them carried it as `americanPrice`.

Do not compare a one-way `americanPrice` against a two-way line as though they were quoted the same way. A two-way market splits its margin across both sides while a single published price carries all of it, so the one-way number reads shorter than a two-way line at a comparable number.

One row is one rung of the book ladder. `isMain` is true for the line the book was actually leading with, false for an alternate rung, and null where it is not known for that row. Null is not a hint that it was an alternate: older rows predate the distinction and it cannot be recovered, so filter on `isMain === true` when you want main lines only and expect null rows to be excluded.

An alternate rung carries one price in `americanPrice` and nothing in the side fields, and it reflects the ladder as it stood at the last pre-game capture, so it has no opening of its own. Only the main line carries open-to-close movement.

CHECK `closing.capturedAt` BEFORE TREATING THESE AS CLOSING NUMBERS. A main line is the last quote we hold with BOTH sides priced, and books commonly go one-way as tip approaches, so the two-way number can predate the game by hours. Measured on one NBA game, the median gap between the last quote of any kind and the last two-sided one was 187 minutes. The price is real, it is simply not always recent, and capturedAt is how you tell.

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.

## Query parameters

- `eventId` string
- `sport` string
- `player` string
- `propType` string
- `book` string
- `isMain` string
- `startDate` string
- `endDate` string
- `limit` string
- `offset` string

## Response `200`

OK

- HistoricalPlayerPropsResponse
  - `data` object
    - `pagination` object
      - `hasMore` boolean
      - `limit` number, double
      - `offset` number, double
      - `total` number, double
    - `props` HistoricalPlayerPropRow[]
      - `book` string
      - `closing` object
        - `line` number, double
        - `overPrice` number, double, nullable
        - `underPrice` number, double, nullable
      - `eventId` string
      - `gameDate` string
      - `opening` object
        - `line` number, double
        - `overPrice` number, double, nullable
        - `underPrice` number, double, nullable
      - `playerName` string
      - `propType` string
      - `sport` string
  - `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)
