INDEPENDENT DEVELOPER REFERENCE
DraftKings API in Python and cURL
Call the API with cURL and the Python standard library: timeouts, retries on 503, bounded pagination and clear error handling.
Editorial review: · Not a live coverage report
No SDK needed
The API is plain HTTPS with JSON responses. cURL and Python's standard-library urllib both work, and the examples here install nothing. Each reads DRAFTKINGS_API_KEY from your environment and contains no credential.
curl --fail-with-body "https://draftkingsapi.com/api/v1/pick6/projections?sport=NBA&limit=10" \
-H "Authorization: Bearer $DRAFTKINGS_API_KEY"--fail-with-body makes cURL exit non-zero on an HTTP error while still printing the JSON error. Scripts then stop rather than parse an error as data.
A small Python client
This client sets a timeout, retries transient failures with backoff, reads the error envelope and stops at a page budget. Python 3.9 or later is enough.
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
BASE = os.environ.get("DRAFTKINGS_API_BASE", "https://draftkingsapi.com")
KEY = os.environ["DRAFTKINGS_API_KEY"]
def get(path, params=None, attempts=3):
query = urllib.parse.urlencode({k: v for k, v in (params or {}).items() if v is not None})
url = f"{BASE}{path}?{query}" if query else f"{BASE}{path}"
request = urllib.request.Request(url, headers={"Authorization": f"Bearer {KEY}"})
for attempt in range(1, attempts + 1):
try:
with urllib.request.urlopen(request, timeout=20) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = json.loads(error.read() or b"{}")
message = body.get("error", {}).get("message", "unknown error")
# 429 means wait for a UTC boundary, so a short retry only spends time.
if error.code != 503 or attempt == attempts:
raise RuntimeError(f"{error.code}: {message}") from error
except urllib.error.URLError:
if attempt == attempts:
raise
time.sleep(2**attempt)
def pages(path, params, max_pages=3, limit=100):
offset = 0
for _ in range(max_pages):
page = get(path, {**params, "limit": limit, "offset": offset})
yield from page["data"]
if not page["pagination"]["has_more"]:
return
offset += limit
for row in pages("/api/v1/pick6/projections", {"sport": "NBA"}, max_pages=2):
if not row["stale"] and not row["is_paused"]:
print(row["stat"], row["target_value"], row["observed_at"])Retry only what can recover
Retrying everything wastes quota, because authenticated calls count even when they return nothing useful. Choose by status.
| Status | Retry? | Why |
|---|---|---|
| 400 | No | Fix the query. Unknown or duplicate parameters will fail the same way. |
| 401, 403 | No | Fix the key or the access boundary. Never try to bypass a 403. |
| 429 | Not in a loop | The allowance resets at a UTC minute or month boundary. Sleep until then. |
| 503 | A few times, with backoff | Storage or a dataset may be briefly unavailable. Check status if it persists. |
| Network error | A few times, with backoff | No response arrived, so the outcome is unknown. |
Paginate deliberately
On Pick6, DFS, sportsbook and context lists, limit defaults to 25 and accepts 1 to 100, and offset defaults to 0 and accepts 0 to 10,000. Continue while pagination.has_more is true, adding limit to offset. Captures can change across calls, so pagination is not a frozen snapshot.
Statistics endpoints use a cursor instead, and Pick6 history uses after. The pagination guide has one working loop for each style.
Budget your calls
Free includes 500 requests/month at 20/minute; Developer is $49/month with 10,000 requests/month at 60/minute; Production is $149/month with 50,000 requests/month at 120/minute. Limits are shared across keys. Existing legacy accounts retain their original daily terms, and the dashboard shows the active allowance.
Valid authenticated calls count even for empty results or unavailable datasets, and invalid queries authenticated before validation count too. Keep a page cap in every script, as max_pages does above, so a bug cannot spend a month of quota overnight.
Cache responses in your own process or database for at least the collector interval. The API sends Cache-Control: no-store, and polling faster than the worker target returns no newer data.
Filter to what you can use
Filter on the server where a parameter exists, then apply capture checks on the client. sport and stat reduce pages and quota on Pick6. sport, event_id and market do the same on odds. Freshness has no server filter, so check stale per row.
curl --fail-with-body "https://draftkingsapi.com/api/v1/odds?sport=NBA&market=total&limit=25" \
-H "Authorization: Bearer $DRAFTKINGS_API_KEY"Unsupported parameters return 400 rather than being ignored, so a typo fails loudly. Read the valid filters in the docs or the machine-readable OpenAPI document.
Try it
Run the downloadable script, which applies the same retry and page limits: python3 draftkings-quickstart.py --sport NBA --limit 5.
Endpoint docs
Working assets
Questions
Do I need a third-party Python package?
No. The examples use urllib and json from the standard library. You can use requests or httpx if your project already has them, because the API is ordinary HTTPS with a Bearer header.
Why does my script get a 400 for a parameter that looks harmless?
The API rejects unknown and duplicate query parameters. Check the parameter name against the docs. Each endpoint has its own list of accepted filters.
Should I retry a 429 automatically?
Not in a tight loop. A 429 clears at the next UTC minute or the next calendar month, depending on which limit you hit. Sleep to the next minute once, and stop if it fails again.
Related resources
For developers who want a response now: a keyless sample, then a free key, then an authenticated call. Not for production hardening or sportsbook player props.
DraftKings API in JavaScript and Node.jsCall the API from Node.js with built-in fetch: timeouts, retries, async pagination and a server route that keeps your key private.
DraftKings API pagination: offset, after and cursorsThree pagination styles with working loops: offset lists, the Pick6 next_after sequence and statistics cursors, plus their limits.
Do I need an SDK to use the API?No. It is plain HTTP with a Bearer header, so cURL, Python urllib and Node fetch all work. There is no vendor package to install.
What are the API request limits?Free is 500 requests a month at 20 a minute; paid plans raise both. Limits are shared across keys and reset on UTC boundaries.
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.