INDEPENDENT DEVELOPER REFERENCE
DraftKings API pagination: offset, after and cursors
Three pagination styles with working loops: offset lists, the Pick6 next_after sequence and statistics cursors, plus their limits.
Editorial review: · Not a live coverage report
Which style each endpoint uses
The API has three pagination styles, and each endpoint uses one. Using the wrong parameter returns 400.
| Endpoints | Style | Page size | Sort order |
|---|---|---|---|
/events, /odds | offset | limit 1 to 100, default 25 | Row id |
/history | offset | limit 1 to 100, default 25 | seq |
/pick6/projections | offset | limit 1 to 100, default 25 | pick_group_id, pickable_id, pickable_market_id |
/pick6/history | after with offset=0 | limit 1 to 100, default 25 | seq, returned as change_id |
/dfs/* | offset | limit 1 to 100, default 25 | Row id |
/context/* | offset | limit 1 to 100, default 25 | sport, event_id, entity_id |
/stats/* | after from pagination.next | limit 1 to 200, default 50 | Event and row key |
/api/v1/coverage takes no parameters, and every parameter name can appear only once in a query.
Offset pagination
offset defaults to 0 and accepts 0 to 10,000. Continue while pagination.has_more is true, adding limit each time. Always set a page cap in your code.
offset = 0
for _ in range(max_pages):
page = get("/api/v1/odds", {"sport": "NBA", "limit": 100, "offset": offset})
handle(page["data"])
if not page["pagination"]["has_more"]:
break
offset += 100Beyond an offset of 10,000 the request returns 400. If you reach that, narrow the query by sport, stat, market or event_id and page each subset.
Pick6 history with next_after
Pick6 history is an ordered change log. Start with after=0 and limit=100, then send pagination.next_after as the next after, keeping offset at 0. Each row's change_id is its sequence number.
after = load_cursor() # 0 on the first run
for _ in range(max_pages):
page = get("/api/v1/pick6/history", {"sport": "NHL", "after": after, "limit": 100})
store_changes(page["data"]) # idempotent on change_id
after = page["pagination"]["next_after"]
save_cursor(after) # only after the rows are safely stored
if not page["pagination"]["has_more"]:
breakSave the cursor after processing, not before. If the process dies mid-page, the next run repeats that page, and storing rows by change_id makes the repeat harmless. An empty page returns next_after equal to the after you sent, so waiting and asking again is safe.
/api/v1/changes is an alias for this endpoint. It covers Pick6 only. Sportsbook history is /api/v1/history, which uses offset.
Statistics cursors
/api/v1/stats/games, player-games and team-games use an opaque cursor. sport and season are required. Send pagination.next back as after until it is null. offset and format are rejected.
after = None
for _ in range(max_pages):
page = get("/api/v1/stats/player-games",
{"sport": "NFL", "season": 2026, "limit": 200, "after": after})
handle(page["data"])
after = page["pagination"]["next"]
if after is None:
breakDo not parse or build the cursor yourself. An empty matching page returns available: false, which means no stored rows match and says nothing about whether the games happened.
Pages are not a frozen snapshot
Collectors keep writing while you page. A row can move between pages, or change, between your calls, so offset lists can skip or repeat rows at the boundaries. Treat a paginated read as a sequence of observations.
- Deduplicate by the row's identity:
idon odds and DFS rows, the three-part tuple on Pick6 rows. - Keep the newest
observed_atwhen you see the same row twice. - Use Pick6 history when you need every change, because its sequence order is stable.
- Pick6 stream snapshots read atomically, on plans that include the stream. See the webhook guide.
A full page is not evidence of complete coverage either. has_more: false means you reached the end of stored matching rows, not the end of the source's board.
Spend fewer requests
Every page costs one request whatever its size, so use the largest page you can handle. Reading 1,000 rows costs 10 requests at limit=100 and 40 at the default of 25. Filter first, and stop paging once you have what you need.
With a 20-per-minute limit on Free, a tight loop can hit 429 before any monthly cap. Space pages if you expect long reads, and set a page budget for every job.
Try it
curl --fail-with-body "https://draftkingsapi.com/api/v1/pick6/history?sport=NHL&after=0&limit=25" \
-H "Authorization: Bearer $DRAFTKINGS_API_KEY"Endpoint docs
Questions
What is the maximum page size?
100 rows on Pick6, DFS, sportsbook and context lists, and 200 on statistics endpoints. Asking for more returns 400.
Why did a row appear on two pages?
Captures can change while you page, because pagination is not a frozen snapshot. Deduplicate by row identity and keep the newest observation.
Does an empty page cost a request?
Yes. Valid authenticated calls count even when no rows match, so stop when has_more is false rather than requesting another page to check.
How do I read more than 10,000 rows?
Offset stops at 10,000. Split the query with filters such as sport, stat, market or event_id, or use the Pick6 change sequence, which continues with after.
Related resources
Call the API with cURL and the Python standard library: timeouts, retries on 503, bounded pagination and clear error handling.
Track DraftKings line movement with the APIFollow recorded Pick6 changes with a resumable cursor, read sportsbook history by sequence, and compute moves without overclaiming.
DraftKings API errors and rate limitsWhat each status means, what counts against your plan, when limits reset, and how to write retry logic that does not waste quota.
What is the maximum page size?Most lists allow limit up to 100 with offset up to 10,000. Statistics use a cursor with limit up to 200, and Pick6 history uses after.
Independent service operated by DD Inc., not affiliated with or endorsed by DraftKings. Technical access is not a redistribution license. No wagering: this service does not place, recommend or settle entries or bets. Check sources and status for actual capture context.