---
title: "NHL player-prop archive: every stored observation for a game or a player"
method: GET
path: "/v1/odds/player-props/historical"
tags: ["odds"]
---

# NHL player-prop archive: every stored observation for a game or a player

`GET /v1/odds/player-props/historical`

## Request

- Base URL: `https://api.bigballsdata.com`
- URL: `https://api.bigballsdata.com/v1/odds/player-props/historical`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Every stored NHL player-prop observation for one game or one player,
newest snapshot first. **16,807,052 observations across 4,231 events,
2023-05-03 to 2026-06-14**, 18 vendor markets over 9 canonical stats.
Requires an Edge plan or higher (Free / Solo / Trio receive 403).

This is the ARCHIVE. It is a different product from
`/v1/nhl/players/{player_id}/props`, which serves the CURRENT line:
one row per book and market type, picked by a stated precedence. Here
nothing is collapsed: the response grain is one observation, and season,
snapshot minute, book, market, market type, line and outcome are each
carried on their own row and each addressable as a filter. That is what
lets you reconstruct line movement yourself.

**One of `match_id` or `player_id` is required.** An unanchored window
query cannot be answered from the archive's indexes, so it is refused
rather than accepted and timed out.

**Unattributed rows are served, not dropped.** Roughly 4% of
observations name a player the bridge could not pin to exactly one of
our players. Those rows are returned with `player_id: null`, the
vendor's `player_name` intact, and `attribution` set to `ambiguous` or
`unmatched`, never guessed onto a player id. `meta.attribution_counts`
gives the split for YOUR selection. Note that a `player_id`-addressed
query cannot return them at all, by construction; `meta.
unattributed_rows_reachable` says so on every response.

**There is no `opening` or `closing` label, deliberately.** The board
opens roughly 48h before puck drop, so no stored row is provably an
opening price. Each row carries `snapshot_at` and the game's
`commence_time`, plus `before_kickoff`, which is exactly the
comparison `snapshot_at < commence_time`, made at read time. Derive
"closing" as the last observation strictly before `commence_time`.

Response: `{ data: [{ match_id, home_team, away_team, commence_time,
player_id, player_name, attribution, market, market_type, outcome, line,
price_american, book: { id, name, logo_url }, source, snapshot_at,
before_kickoff }], meta: { total, limit, offset, addressed_by,
attribution_counts, unattributed_rows_reachable, markets_selected,
snapshot_labels, generated_at } }`.

## Query parameters

- `match_id` string, uuid
- `player_id` string, uuid
- `market` 'shots_on_goal' | 'goals' | 'assists' | 'points' | 'saves' | 'power_play_points' | 'blocked_shots' | 'goals_against' | 'shutouts'
- `market_type` 'over_under' | 'alternate' | 'scorer_anytime' | 'scorer_first' | 'scorer_last'
- `sportsbook` string
- `snapshot_from` string, date-time
- `snapshot_to` string, date-time
- `before_kickoff` 'true' | 'false'
- `attribution` 'resolved' | 'ambiguous' | 'unmatched' | 'unresolved'
- `limit` integer
- `offset` integer

## Response `200`

ok

## Other responses

- `400`: invalid query: neither match_id nor player_id given, or a market/market_type pair no market has
- `403`: plan_required (Edge or higher)
- `404`: no archived event for this match, or more than one and it cannot be disambiguated

---

[API](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api.md) · [All operations](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api/revisions/31fb54ac414e?raw)
