prizepicksapi

JavaScript & Node.js

Use the PrizePicks API from JavaScript and Node.js.

Node.js 18 and later ship fetch, so no client library is needed. This guide gives a runnable script, then shows how to serve the data to a browser without exposing your API key.

Run a Node.js script with built-in fetch

Save this as prizepicks.mjs and run node prizepicks.mjs. Without PRIZEPICKS_API_KEY it reads the public demo endpoint, whose records are fictional and marked is_demo=true. With the variable set, the same code reads stored live-source observations.

// prizepicks.mjs  (Node.js 18+; no dependencies)
const API_KEY = process.env.PRIZEPICKS_API_KEY;
const BASE = "https://prizepicksapi.com";
const PATH = API_KEY ? "/api/v1/projections" : "/api/v1/demo/projections";

class ApiError extends Error {
  constructor(status, code, message, retryAfter) {
    super("HTTP " + status + " " + code + ": " + message);
    Object.assign(this, { status, code, retryAfter });
  }
}

async function getPage(params) {
  const url = new URL(PATH, BASE);
  for (const [key, value] of Object.entries(params)) {
    if (value !== undefined && value !== null) url.searchParams.set(key, String(value));
  }
  const headers = { Accept: "application/json" };
  if (API_KEY) headers.Authorization = "Bearer " + API_KEY;
  const res = await fetch(url, { headers, signal: AbortSignal.timeout(20_000) });
  const body = await res.json().catch(() => null);
  if (!res.ok) {
    const err = body?.error ?? {};
    throw new ApiError(res.status, err.code ?? "unknown", err.message ?? "", res.headers.get("Retry-After"));
  }
  return body;
}

export async function* projections({ league = "NBA", stat, limit = 100 } = {}) {
  let cursor;
  const seen = new Set();
  for (;;) {
    const page = await getPage({ league, stat, limit, cursor });
    console.error("source:", page.meta.source_status, "observed:", page.meta.observed_at);
    for (const p of page.data) {
      if (API_KEY && p.is_demo) throw new Error("Unexpected demo record in live response");
      yield p;
    }
    cursor = page.meta.next_cursor;
    if (!cursor) return;
    if (seen.has(cursor)) throw new Error("Repeated pagination cursor");
    seen.add(cursor);
    await new Promise((r) => setTimeout(r, 6_100)); // stay under the Free per-minute limit
  }
}

console.error("mode:", API_KEY ? "authenticated observations" : "fictional demo");
for await (const p of projections()) {
  console.log(p.id, p.player.name, p.market.stat, p.market.period, p.line);
}

The error class keeps the HTTP status, the machine-readable error.code from the response body, and any Retry-After header. That lets a caller tell a revoked key (401) from a per-minute limit (429 RATE_LIMIT_EXCEEDED) or an exhausted monthly allowance (429 MONTHLY_QUOTA_EXCEEDED) without parsing message text.

Serve data to a browser through your own backend

A frontend should request your server, not prizepicksapi.com. The server authenticates your user, validates parameters, calls the API with the secret key, and caches a shared response so one popular page does not spend one request per visitor. The example below is a Next.js App Router route handler; the same shape works in Express, Hono, or a serverless function.

// app/api/props/route.ts in YOUR application
let cache: { key: string; at: number; body: unknown } = { key: "", at: 0, body: null };

export async function GET(request: Request) {
  // 1. Authenticate your own user here before spending shared quota.
  const league = (new URL(request.url).searchParams.get("league") ?? "NBA").toUpperCase();
  if (!/^[A-Z0-9]{2,12}$/.test(league)) return Response.json({ error: "invalid league" }, { status: 400 });

  // 2. Reuse a recent response instead of fetching per visitor.
  if (cache.key === league && Date.now() - cache.at < 60_000) return Response.json(cache.body);

  const upstream = await fetch(
    "https://prizepicksapi.com/api/v1/projections?limit=100&league=" + encodeURIComponent(league),
    { headers: { Authorization: "Bearer " + process.env.PRIZEPICKS_API_KEY }, cache: "no-store" },
  );
  const body = await upstream.json();
  if (!upstream.ok) return Response.json({ error: body?.error?.code ?? "upstream_error" }, { status: 502 });

  // 3. Forward freshness metadata so the UI can label stale data.
  const out = { data: body.data, source_status: body.meta.source_status, observed_at: body.meta.observed_at };
  cache = { key: league, at: Date.now(), body: out };
  return Response.json(out);
}

The single-entry in-memory cache is deliberately minimal; a multi-instance deployment needs a shared cache. Forwarding source_status and observed_at matters: a browser table should be able to label a stale collection rather than presenting every 200 response as a live board.

Type the response once

If you use TypeScript, model nullable fields as nullable. selections[].available is boolean | null, selections[].payout_multiplier is number | null, and player.team, event.starts_at, and source_updated_at may be null. Narrow these explicitly in UI code instead of using a non-null assertion. The field reference guide lists every field with its meaning.

Common JavaScript mistakes

  • Concatenating query strings by hand. Use URL and searchParams so player names with accents or spaces are encoded.
  • Treating the first page as the whole board. Follow meta.next_cursor until it is null.
  • Running Promise.all over many pages. Pages are sequential by design, and parallel bursts reach the per-minute limit quickly.
  • Coercing null to 0 with Number(value) or value || 0. Unknown is not zero.
  • Logging request headers in error handlers, which writes the Bearer token into logs.

Frequently asked questions

Is there an official npm package?

No package is published for this service. The documented HTTP endpoints work with the fetch built into Node.js 18+, Deno, Bun, and modern browsers.

Can I call the API directly from browser JavaScript?

You should not. Live endpoints need a Bearer key, and any key shipped to a browser can be extracted. Call your own backend, which holds the key and applies your user authorization.

References

Current API documentation ↗Machine-readable OpenAPI contract ↗MDN: Fetch API ↗MDN: AbortSignal.timeout() ↗

PrizePicks API is an independent data service. It is not affiliated with or endorsed by PrizePicks. Observations, completeness, and freshness are separate claims; consult source status before relying on a stored line.