PrizePicks line history, as a CSV you can open.
One full day of recorded board changes for a league: every new offer, line move, pull and restore, timestamped. Recording started 27 October 2026.
Coverage calendar
Recorded change events per league per US Eastern day, with collector runs per day. Empty cells with runs are quiet boards; days with no runs are collection gaps.
The calendar loads from the recorded history and is paused while the data service is unavailable.
Columns
| Column | Meaning |
|---|---|
| change_id | Sequence id of the event. Same value as id and cursor in GET /api/v1/changes. |
| observed_at | UTC time the collector saw the change (ISO 8601). |
| league | League code as used by the API. |
| event_type | offer.new, offer.changed, offer.unavailable or offer.restored. |
| projection_id | Stable projection id. Use it with GET /api/v1/projections/{id}/history. |
| player | Player name as shown on the board. |
| team | Team abbreviation from the latest stored projection. |
| stat | Market name, for example Points or Pts+Rebs+Asts. |
| period | Market period, for example Full game. |
| more_tier | Tier of the More side on the latest stored projection: standard, goblin or demon. |
| previous_line | Line before this event. Empty for offer.new. |
| line | Line after this event. |
| line_delta | line minus previous_line when both are numbers, else empty. |
RFC 4180 CSV, UTF-8, CRLF line endings. Text that a spreadsheet would run as a formula is prefixed with a single quote. Response headers carry X-Sample-Date, X-Row-Count and X-Truncated.
Load it
import pandas as pd
url = "https://prizepicksapi.com/data/history-sample/sample.csv?league=NBA"
df = pd.read_csv(url, parse_dates=["observed_at"])
moves = df[(df.event_type == "offer.changed") & df.line_delta.notna() & (df.line_delta != 0)]
print(moves.groupby("stat").line_delta.describe())
# Biggest movers of the day
print(moves.reindex(moves.line_delta.abs().sort_values(ascending=False).index)
.head(10)[["observed_at", "player", "stat", "previous_line", "line"]])// Same events, any day since recording began. Node 18+.
const KEY = process.env.PRIZEPICKS_API_KEY;
let cursor = "0";
for (;;) {
const url = new URL("https://prizepicksapi.com/api/v1/changes");
url.search = new URLSearchParams({ league: "NBA", after: cursor, limit: "200" });
const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data, meta } = await res.json();
for (const e of data) console.log(e.observed_at, e.type, e.player_name, e.previous_line, "->", e.line);
if (!meta.next_cursor) break;
cursor = meta.next_cursor;
await new Promise((r) => setTimeout(r, 6100)); // stay under the Free per-minute limit
}Questions
How far back does recorded PrizePicks history go?
Recording started on 27 October 2026. History exists from the first time this service observed an offer; nobody can backfill lines that were not recorded at the time. The calendar on this page shows every recorded day by league.
What is in the CSV sample?
Every change event recorded for one league on the most recent completed US Eastern day: new offers, line moves, offers taken down and offers restored, each with its UTC timestamp, previous and new line. It is the same data as GET /api/v1/changes, flattened to one row per event.
Can I download other days or leagues?
The free sample covers the latest completed day per league. Any day since recording began is available through GET /api/v1/changes and GET /api/v1/projections/{id}/history on every plan, including Free.
Does a day with no events mean data is missing?
Not always. The calendar shows collector runs per day next to each league. Runs with no events mean the league had no board that day; no runs means the collector was down and that day is a real gap.