INDEPENDENT DEVELOPER REFERENCE
DraftKings API in JavaScript and Node.js
Call the API from Node.js with built-in fetch: timeouts, retries, async pagination and a server route that keeps your key private.
Editorial review: · Not a live coverage report
Requirements
Node.js 18 or later includes fetch and AbortSignal.timeout, so nothing needs installing. Set DRAFTKINGS_API_KEY in the server environment. DRAFTKINGS_API_BASE is optional and only useful for pointing tests at a stub.
A small Node.js client
This module sets a 20-second timeout, retries a 503 or network failure with backoff, surfaces the API error message and caps pages. Save it as client.mjs.
const BASE = process.env.DRAFTKINGS_API_BASE ?? "https://draftkingsapi.com";
const KEY = process.env.DRAFTKINGS_API_KEY;
if (!KEY) throw new Error("Set DRAFTKINGS_API_KEY");
async function get(path, params = {}, attempts = 3) {
const url = new URL(path, BASE);
for (const [name, value] of Object.entries(params))
if (value !== undefined) url.searchParams.set(name, String(value));
for (let attempt = 1; ; attempt++) {
let response;
try {
response = await fetch(url, {
headers: { Authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(20_000),
});
} catch (error) {
if (attempt === attempts) throw error;
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
continue;
}
if (response.ok) return response.json();
const body = await response.json().catch(() => ({}));
// Retry 503 only. A 429 clears at a UTC boundary, not in a few seconds.
if (response.status !== 503 || attempt === attempts)
throw new Error(`${response.status}: ${body.error?.message ?? "unknown error"}`);
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
}
}
async function* pages(path, params, maxPages = 3, limit = 100) {
for (let i = 0, offset = 0; i < maxPages; i++, offset += limit) {
const page = await get(path, { ...params, limit, offset });
yield* page.data;
if (!page.pagination.has_more) return;
}
}
for await (const row of pages("/api/v1/odds", { sport: "NBA", market: "total" }, 2)) {
if (!row.stale) console.log(row.event_id, row.side, row.line, row.american);
}Keep the key off the browser
Every endpoint needs a Bearer key, and anything shipped to a browser is public. Put the call in a server route, and return only the fields your interface needs. Treat NEXT_PUBLIC_ and VITE_ variables as public, and never put the key in one.
// app/api/board/route.ts in a Next.js app. The key stays on the server.
export async function GET(request: Request) {
const sport = new URL(request.url).searchParams.get("sport") ?? "NBA";
if (!/^[A-Z0-9 ]{2,16}$/.test(sport))
return Response.json({ error: "Invalid sport" }, { status: 400 });
const upstream = new URL("https://draftkingsapi.com/api/v1/pick6/projections");
upstream.searchParams.set("sport", sport);
upstream.searchParams.set("limit", "25");
const response = await fetch(upstream, {
headers: { Authorization: `Bearer ${process.env.DRAFTKINGS_API_KEY}` },
signal: AbortSignal.timeout(20_000),
});
if (!response.ok)
return Response.json({ error: "Upstream unavailable" }, { status: 502 });
const page = await response.json();
return Response.json(
page.data
.filter((row) => !row.stale && !row.is_paused)
.map((row) => ({
stat: row.stat,
target: row.target_value,
observed_at: row.observed_at,
})),
{ headers: { "Cache-Control": "public, max-age=60" } },
);
}The allowlist on sport stops callers from sending arbitrary filters through your route. The short cache means many visitors share one upstream request, which is what protects your allowance. Public display of this data still needs your own rights review: technical access is not a redistribution license.
Type the rows you use
Type only the fields you read, and mark nullable ones. period, source_updated_at and live_stat_value can be null on real Pick6 rows. Keep selections as an array, because a row can hold one side or two.
type Pick6Selection = {
side: string; // "More", "Less" or "Yes" in current captures
selection_id: string;
standings_multiplier: number | null;
};
type Pick6Row = {
pick_group_id: string;
pickable_id: string;
pickable_market_id: string;
sport: string;
stat: string;
target_value: number;
period: string | null;
is_live: boolean;
is_paused: boolean;
selections: Pick6Selection[];
observed_at: string;
source_updated_at: string | null;
age_seconds: number;
stale: boolean;
};The response schema guide lists fields for every dataset. The OpenAPI document is suitable for generating a full client if you prefer one.
Page cursors in Node
Pick6 history moves with after rather than offset. Save the cursor only after you have processed a page, so a crash repeats work instead of losing it.
let after = await loadCursor(); // from your database, starting at 0
for (let i = 0; i < 5; i++) {
const page = await get("/api/v1/pick6/history", { sport: "NHL", after, limit: 100 });
await saveChanges(page.data); // make this idempotent on change_id
after = page.pagination.next_after;
await storeCursor(after);
if (!page.pagination.has_more) break;
}loadCursor, saveChanges and storeCursor are yours to implement. get is the helper from the client above.
Common Node mistakes
These cost real quota or leak keys, and each has a short fix.
- Running
Promise.allover many pages. Sequential calls respect the per-minute limit; parallel ones hit it quickly. - Calling the API on every page render. Cache in your server, at least for the collector interval shown on status.
- Treating
lineon an odds row as a number. Current rows return it as a numeric string or null, so parse it. - Logging full request headers. That writes the key into your logs.
Try it
Download the script and run node draftkings-quickstart.mjs --sport NBA --limit 5.
Endpoint docs
Working assets
Questions
Can I call the API directly from a React component?
Do not. The key would ship to every visitor. Call your own server route, and let the server call this API with the key from its environment.
Is there an official npm package?
No package is needed or offered. Built-in fetch handles the API, and the OpenAPI document can generate a typed client if you want one.
Which Node.js version do the examples need?
Node.js 18 or later, for built-in fetch and AbortSignal.timeout. The examples use only built-in modules.
Related resources
Call the API with cURL and the Python standard library: timeouts, retries on 503, bounded pagination and clear error handling.
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.
Can I call the API from browser JavaScript?Keep the key on a server. A browser bundle would expose it, and data endpoints do not send cross-origin headers. Use a server route.
DraftKings API developer starter kitDownload Python and Node examples, a notebook, Postman templates, a daily archive workflow and a comparison feed. Bounded requests and no embedded credentials.
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.