DraftKings serves odds across 25+ US states, and its API surface changes often enough that scrapers written six months ago regularly break without warning. if you’re building a line-movement tracker, an arbitrage scanner, or a model that needs real-time DraftKings data, this guide covers what the site actually exposes, which endpoints are stable, and how to stay in the game when bot mitigations tighten.
What DraftKings Exposes (and What It Doesn’t)
DraftKings does not publish a public API. but it does make internal XHR calls that are observable in DevTools, and those calls return structured JSON that’s far cleaner than scraping rendered HTML.
the two most useful endpoints as of 2026:
- odds endpoint:
https://sportsbook.draftkings.com/sites/US-SB/api/v5/eventgroups/{eventGroupId}/categories/{categoryId}/subcategories/{subcategoryId} - featured lines:
https://sportsbook.draftkings.com/api/odds/v1/leagues/{leagueId}/schedule
the eventGroupId maps to sport (e.g., 42648 for NFL, 42133 for NBA). you can enumerate these by hitting the nav endpoint and parsing the eventGroup tree. the response includes spread, moneyline, and total in a nested structure under eventGroupViews[].offerCategories[].offerSubcategoryDescriptors.
state routing is handled via cookies and geolocation headers, not subdomain — which matters when you’re managing proxies for multi-state coverage. for a full breakdown of how multi-book infrastructure handles this, see How to Scrape Betting Odds from Multiple Bookmakers.
Browser Fingerprinting and Bot Detection in 2026
DraftKings runs Akamai Bot Manager on its sportsbook subdomain. Akamai v3 checks canvas fingerprint, WebGL renderer, audio context, and TLS JA3 signature. a plain requests session will get 403’d within a few calls.
your realistic options in 2026:
| Approach | Bypass Rate | Latency | Cost |
|---|---|---|---|
| Playwright + residential proxy | high | 2-5s | $$$ |
| curl-impersonate (TLS spoof only) | medium | <1s | $ |
| Scrapy + Zyte Smart Proxy | high | 1-3s | $$ |
| Selenium Wire + BrightData | high | 2-4s | $$$$ |
| Raw requests (no fingerprint) | very low | <1s | $ |
for casual data pulls (once per hour, few events), curl-impersonate with a rotating residential IP is often enough. for real-time line movement at sub-minute intervals, you need a full browser context. Playwright with playwright-stealth and rebrowser-patches is the most practical stack in 2026.
FanDuel uses a similar Akamai stack — if you’ve already solved it there, the same proxy rotation logic applies. see How to Scrape FanDuel Sportsbook Odds Programmatically (2026) for side-by-side notes.
A Minimal Working Scraper
the cleanest approach: intercept the XHR response in Playwright rather than parsing the DOM.
from playwright.async_api import async_playwright
import json, asyncio
async def get_nfl_odds():
captured = []
async def handle_response(response):
if "eventgroups" in response.url and response.status == 200:
try:
data = await response.json()
captured.append(data)
except Exception:
pass
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
ctx = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
locale="en-US",
timezone_id="America/New_York",
)
page = await ctx.new_page()
page.on("response", handle_response)
await page.goto("https://sportsbook.draftkings.com/leagues/football/nfl")
await page.wait_for_timeout(4000)
await browser.close()
return capturedset your proxy in browser.new_context(proxy={"server": "..."}). if you’re using rotating residential IPs, pin the session to a single exit node for the duration of a page load — mid-session IP rotation breaks Akamai’s session validation and causes re-challenges.
Handling State Restrictions
DraftKings checks your IP geolocation to decide which state’s odds to serve. some lines differ by state: a PA player sees different juice on parlays than an NJ player. if you need multi-state data:
- maintain one proxy session per state (not one pool shared across all states).
- use a proxy provider with city-level targeting — state-level is not granular enough for major metros that straddle state lines.
- warm each session by visiting the homepage first, then navigate to the sport. cold direct-to-API calls have higher rejection rates.
- store the
DK_SB_SESSIONandDK_BETScookies between requests — re-authenticating from scratch on every call increases fingerprint variance.
BetMGM has a similar state-partitioned structure. How to Scrape BetMGM Lines Across States (2026) documents the header patterns that differentiate state responses.
Parsing the Response Structure
the DraftKings JSON response is deeply nested. here’s what to navigate to reach a spread line:
eventGroupViews
└── [0].offerCategories
└── find where name == "Game Lines"
└── offerSubcategoryDescriptors
└── offerSubcategory.offers
└── [event_index]
└── outcomes ← spread, total, moneyline hereeach outcome object contains:
label: “Over”, “Under”, home team name, away team nameoddsAmerican: the displayed moneyline (e.g., “-110”)line: the spread or total value (e.g., 47.5)providerIdandproviderOfferId: useful for deduplication across updates
store the full raw JSON per pull with a timestamp. odds can move 3-4 times per hour on NFL Sundays. if you’re doing line movement analysis, diffing the raw payloads is more reliable than trying to detect changes at the outcome level.
for sharp-money modeling, compare DraftKings lines against Pinnacle — Pinnacle’s closing lines are the sharpest benchmark available. How to Scrape Pinnacle Sports Lines for Sharp Models (2026) covers the Pinnacle API structure in detail.
Common Errors and What They Mean
| HTTP Status | Akamai Header | Cause | Fix |
|---|---|---|---|
| 403 | X-Check-Cacheable: NO | bot fingerprint flagged | rotate IP + reset browser context |
| 429 | Retry-After: 60 | rate limit hit | back off, increase interval |
| 200 (empty body) | – | geo-blocked state | verify proxy exit state |
| 302 → login | – | session cookie expired | re-warm session |
one non-obvious gotcha: a 200 with an empty eventGroupViews array is not an error. it usually means you hit the endpoint before the page’s JS has fully initialized the sport context. add a 3-4 second wait or poll until the array is non-empty.
if you’re building against Bet365 in parallel (for arbitrage), note that Bet365’s bot detection is significantly heavier. How to Scrape Bet365 Odds Around the World (2026) documents what additional layers are in play.
Bottom Line
DraftKings is scrapeable in 2026 with Playwright and a residential proxy pool — the XHR interception approach is more stable than endpoint polling because it’s tied to actual user navigation, which is exactly what Akamai expects. prioritize session warmup, per-state proxy pinning, and raw JSON archiving from day one. DRT will continue covering sportsbook scraping infrastructure as detection stacks evolve through 2026.