draftkings apiDashboard ↗

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.

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.

Server route with a short cache
// 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.

Minimal TypeScript shape
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.

Resume from a stored cursor
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.all over 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 line on 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.

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

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.