INDEPENDENT DEVELOPER REFERENCE
DraftKings API freshness: clocks and stale rows
How to read observed_at, source_updated_at, age_seconds and stale, what worker targets mean, and how to label data honestly.
Editorial review: · Not a live coverage report
Four fields tell you the age
Sportsbook, Pick6 and context rows carry the fields below. Check them on each row, because one page can mix fresh and older observations.
| Field | Meaning | Common mistake |
|---|---|---|
observed_at | When our collector captured this row | Describing it as when the price changed |
source_updated_at | The venue's own update clock, null when unknown | Filling the null with observed_at |
age_seconds | Seconds between observed_at and the response | Caching it, since it is computed per request |
stale | True when age_seconds is over 900, which is 15 minutes | Treating false as a promise the offer is live |
DFS rows carry observed_at, received_at and http_date, but no age_seconds or stale, so compute age yourself. Statistics rows hold their clocks under evidence.
How often collection runs
Worker targets are five minutes for sportsbook and Pick6, and fifteen minutes for DFS, when enabled. These are targets, not a service level. Each run reads a bounded set of categories, events and draft groups, so a given row may not refresh on every run.
- Pick6 rotates through selected sport and category pages, so one category can lag another.
- Sportsbook collection shares a request budget across leagues, with priority for live and pregame events.
- DFS covers selected draft groups, rotating fairly among them.
- A source 403 or 429 pauses that collector for at least an hour.
Sources and status shows actual last successes and failures. Current counts and ages are measured there and in the coverage report, not promised here.
Check freshness in code
Apply one rule everywhere: filter by stale, then decide per use case how old is too old. Use a stricter threshold than 15 minutes when you need it, because age_seconds lets you choose.
MAX_AGE_SECONDS = 600 # your own threshold, 15 minutes or less
usable, excluded = [], 0
for row in page["data"]:
if row["stale"] or row["age_seconds"] > MAX_AGE_SECONDS or row.get("is_paused"):
excluded += 1
else:
usable.append(row)
print(f"{len(usable)} usable, {excluded} excluded")Report excluded rows in logs or in the interface. A board that went empty because everything is stale should not look like a slow news day.
Check the whole dataset
/api/v1/coverage reports measured rows and collector context per dataset, including fresh and retained Pick6 counts by sport. Use it before a job, and when a page looks thin. Pick6 responses also include a coverage object with market_rows, observed_at, age_seconds and stale.
curl --fail-with-body "https://draftkingsapi.com/api/v1/coverage" \
-H "Authorization: Bearer $DRAFTKINGS_API_KEY"/api/health is public and also returns a coverage summary, so a monitor can watch it without using your allowance.
What each clock cannot tell you
observed_at shows when we saw a value, not when the value first existed. A price could have changed minutes before our capture. Move timing from recorded rows has the same limit, so it is bounded by capture cadence.
- A live-stat clock,
live_stat_updated_at, belongs to the stat. It is not the offer clock. - Absence from a page is not settlement or removal, because category captures are partial.
event_status: postmeans the game finished. Rows for it are historical quotations.- Statistics captured after a game do not show what was known before it.
Label data honestly in your product
Show the capture time next to any value a person reads, in words like "Observed 4 minutes ago". Avoid "live" unless you have checked the age yourself. When data is stale, say so rather than hide it, and when a request fails, keep showing the last value with its old timestamp.
Cache for at least the collector interval. Responses are no-store on our side, so caching is your decision, and polling more often than the target spends quota with no newer data. If you need change events instead of polling, read webhooks and streams.
Try it
Look at observed_at, age_seconds and stale on each returned row.
curl --fail-with-body "https://draftkingsapi.com/api/v1/odds?sport=NHL&limit=5" \
-H "Authorization: Bearer $DRAFTKINGS_API_KEY"Endpoint docs
Working assets
Check the recorded data
Questions
How fresh is the data?
Worker targets are five minutes for sportsbook and Pick6 and fifteen minutes for DFS, without a freshness guarantee. Check observed_at per row, and the status page for actual last successes.
What does stale mean?
stale is true when a row's capture is more than 900 seconds old. The row stays retained and readable, but it should not be presented as a current offer.
Why is source_updated_at always null?
The sources do not give a reliable venue clock for these rows, so the field stays null. observed_at is our capture time and is the only clock you should display.
Is this real-time data?
No. It is stored observations collected on bounded schedules. There is no latency guarantee, and it should not be used where seconds matter.
Related resources
Native sportsbook streams price changes. Scheduled datasets rotate through captures; row timestamps and scope health show current freshness.
Why does a row say stale: true?Its last observation is over 15 minutes old. Retained rows stay readable after collection moves on, so stale marks old evidence, not an error.
DraftKings API webhooks and change streamsSigned Pick6 change webhooks on Production and a resumable stream on Developer: signature checks, retries, replay and limits.
DraftKings API response schema, field by fieldEvery response field for sportsbook, Pick6, DFS, statistics and context rows, with types, null behavior and how to read each one.
statusIndependent 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.