draftkings apiDashboard ↗

INDEPENDENT DEVELOPER REFERENCE

DraftKings API errors and rate limits

What each status means, what counts against your plan, when limits reset, and how to write retry logic that does not waste quota.

Editorial review: · Not a live coverage report

The error envelope

Errors return JSON with a numeric code and a message. Validation failures can add details. Log the status and message, and never the Authorization header.

Error bodies
{"error":{"code":429,"message":"Account request limit reached"}}

{"error":{"code":400,"message":"Invalid input","details":["..."]}}

What each status means

Each status has one sensible response. Retrying is only right for two of them.

StatusMeaningWhat to do
400Invalid, unsupported or duplicate parameter, or a value out of boundsFix the query. Do not retry.
401Missing, malformed, invalid or revoked keyCheck the Authorization: Bearer dk_... header and that the key is active.
403Rejected origin, a plan-gated delivery feature, or the DFS access boundaryRead the message. Upgrade or stop. Do not work around it.
404Unknown resource pathCheck the path against the docs.
409Key or capture conflict, including a sixth active key or a stream cursor ahead of retained changesRevoke a key, or restart from a current snapshot.
410Stream cursor purgedTake a new snapshot and reconnect.
413Stream snapshot over its boundAdd a sport filter.
428Stream opened without a cursorTake a snapshot first, or send start=latest on purpose.
429Monthly or per-minute allowance reachedWait for the relevant UTC boundary.
503Storage or a dataset is unavailable, or a delivery feature is disabledBack off, then check status. Do not assume zero rows.

Plan limits

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 apply to the account, shared across every key. Existing legacy accounts keep their original daily terms, and the dashboard shows your active contract.

PlanPriceRequests per UTC monthRequests per minutePick6 streamSigned webhooks
Free$0/month50020NoNo
Developer$49/month10,00060YesNo
Production$149/month50,000120YesYes

What counts as a request

One authenticated API call is one request, whatever its page size. Authentication runs first, so these all count: empty results, a dataset returning 503, and invalid queries that passed authentication.

  • Each stream connection counts as one request, and connections last up to 55 seconds.
  • Each stream snapshot counts as one request.
  • A failed key check does not reach your account, because the key was not recognized.
  • /api/health and /api/openapi are public and need no key.

When limits reset

The two limits work on different clocks. Both use UTC, and neither follows your invoice date. Requests do not roll over.

LimitResetsTypical cause of a 429
Per minuteAt the next UTC minuteA burst, such as parallel page fetches
Per monthAt the next UTC calendar monthSteady polling that outruns the allowance
Legacy dailyAt the next UTC dayLegacy accounts only

The 429 message does not say which limit you reached, and responses carry no rate-limit or remaining-quota headers. Check the dashboard for current usage. A simple rule works in code: on a first 429, sleep to the next UTC minute and try once. If it fails again, stop the job and alert yourself.

Retry logic that respects quota

Retry 503 and network failures a few times with exponential backoff. Handle 429 by waiting for a boundary. Every other status needs a code or account change, so retrying only spends requests.

Policy sketch in Python
import time
from datetime import datetime, timezone

def seconds_to_next_minute():
    now = datetime.now(timezone.utc)
    return 60 - now.second + 1

def fetch_with_policy(call):
    limited = False
    for attempt in range(1, 4):
        status, body = call()
        if status < 400:
            return body
        if status == 429 and not limited:
            limited = True
            time.sleep(seconds_to_next_minute())
            continue
        if status == 503 and attempt < 3:
            time.sleep(2 ** attempt)
            continue
        raise RuntimeError(f"{status}: {body['error']['message']}")
    raise RuntimeError("Gave up after retries")

Do not bypass a denial

A 403 on DFS or a delivery feature is a real boundary. Do not change hosts, share keys between accounts or scrape the source as a fallback. A 503 is not permission to invent data: return an explicit unavailable state to your own users, with the last observed_at you hold.

Try it

Send a deliberately invalid limit to see a validation error, and note that it spends one request.

curl --fail-with-body "https://draftkingsapi.com/api/v1/odds?limit=101" \
  -H "Authorization: Bearer $DRAFTKINGS_API_KEY"

Check the recorded data

Questions

Does the API send rate-limit headers?

No. Responses do not carry remaining-quota headers. The dashboard shows your active allowance and reset, so count requests in your own client if you need live tracking.

Do multiple keys give me more requests?

No. Limits apply to the account, shared across all active keys.

Do failed requests count?

Valid authenticated calls count even if empty or if a dataset is unavailable. Invalid queries that authenticate before validation also count. Calls with an unrecognized key do not reach an account.

When does my monthly allowance reset?

At the start of the UTC calendar month, not on your invoice date. Unused requests do not roll over.

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.