draftkings apiDashboard ↗

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.

FieldMeaningCommon mistake
observed_atWhen our collector captured this rowDescribing it as when the price changed
source_updated_atThe venue's own update clock, null when unknownFilling the null with observed_at
age_secondsSeconds between observed_at and the responseCaching it, since it is computed per request
staleTrue when age_seconds is over 900, which is 15 minutesTreating 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.

Filter and report what you excluded
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.

One request, no parameters
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: post means 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"

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

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.