The contract in one table
| Endpoint | Cursor type | Where the next cursor is |
|---|---|---|
| GET /api/v1/projections | Opaque token (letters, digits, _ and -) | meta.next_cursor |
| GET /api/v1/projections/{id}/history | Numeric sequence | meta.next_cursor |
| GET /api/v1/changes | Numeric sequence | meta.next_cursor, and each event’s cursor |
| GET /api/v1/export | Opaque token | X-Next-Cursor response header (empty when done) |
Send the value back as cursor. The changes and history endpoints also accept after as an alias, which reads naturally for a feed. Sending both cursor and after, or repeating any parameter, returns 400. A cursor longer than 200 characters or in the wrong format for the endpoint is also rejected.
Walk every page with curl and jq
#!/usr/bin/env bash
set -euo pipefail
cursor=""
: > nba.jsonl
while :; do
page=$(curl -fsS "https://prizepicksapi.com/api/v1/projections?league=NBA&limit=200${cursor:+&cursor=$cursor}" \
-H "Authorization: Bearer $PRIZEPICKS_API_KEY")
echo "$page" | jq -c '.data[]' >> nba.jsonl
next=$(echo "$page" | jq -r '.meta.next_cursor // empty')
[ -z "$next" ] && break
[ "$next" = "$cursor" ] && { echo "repeated cursor" >&2; exit 1; }
cursor=$next
sleep 6.1
done
echo "status: $(echo "$page" | jq -r .meta.source_status) rows: $(wc -l < nba.jsonl)"Each page is one metered request. With limit=200, a 1,000-record board takes five requests; with the default 50 it takes twenty. The pause keeps one serial loop under the Free plan’s per-minute limit. Paid plans allow higher rates, but a loop should still stop on a repeated cursor.
Keep filters identical across pages
A cursor continues a specific filtered collection. If page one used league=NBA&stat=Points, every following page must send the same league, stat, search, tier, and status values. Changing a filter mid-traversal does not raise an error, but the result combines two different collections and is hard to reason about.
A traversal is not a point-in-time snapshot
Each page is evaluated when you request it. With the default status=active, a record must appear in the latest successful collection for its scope and have been confirmed within ten minutes. If a collection finishes while you are paging, later pages reflect the new state. For a consistent dataset, record meta.observed_at on every page and discard or re-run a traversal whose pages straddle different collection times.
Resume a stopped job
- Projections: there is no durable position worth keeping across hours. Restart from the first page; the board may have changed.
- Changes: persist the last processed event’s cursor after your work commits, then resume with after=that value. Events are replayable.
- History: per projection, keep the last entry id and continue with cursor when you need newer entries.
- Export: treat each CSV page like a projections page and keep the X-Next-Cursor value only for the current run.
Frequently asked questions
Can I request page 5 directly?
No. There is no offset or page-number parameter. Pages are reached by following next_cursor from the previous page.
What is the maximum page size?
limit accepts integers from 1 to 200. The default is 50. Values outside that range return 400 invalid_query.
References
Current API documentation ↗Machine-readable OpenAPI contract ↗PrizePicks API is an independent data service. It is not affiliated with or endorsed by PrizePicks. Observations, completeness, and freshness are separate claims; consult source status before relying on a stored line.