# Odds API Node.js quickstart: read the GetOdds gateway

Read a small PrizePicks market sample with Node.js built-in fetch. Keep the key on your server, validate incomplete samples, and handle errors without logging credentials.

Canonical page: https://getoddsapi.com/docs/node

## Free API key

Get a free GetOdds API key: **1,000 requests per UTC day and 60 per minute, no card**. One key covers REST and the hosted MCP server.

Sign up: https://getoddsapi.com/sign-up?ref=ai-claudebot

## Quickstart

1. Create an account at https://getoddsapi.com/sign-up?ref=ai-claudebot, open https://getoddsapi.com/dashboard and issue a key with the sources you need (draftkings, fanduel, betrivers, prizepicks or underdog). Keep it in a server environment variable named GETODDS_API_KEY.
2. Read current DraftKings NBA spreads with curl:

```bash
curl --fail-with-body "https://getoddsapi.com/api/v1/odds?sport=NBA&market=spread&fresh_only=true&limit=50" \
  -H "Authorization: Bearer $GETODDS_API_KEY"
```

3. The same request in Python (standard library only):

```python
import json, os, urllib.request

req = urllib.request.Request(
    "https://getoddsapi.com/api/v1/odds?sport=NBA&market=spread&fresh_only=true&limit=50",
    headers={"Authorization": "Bearer " + os.environ["GETODDS_API_KEY"]},
)
with urllib.request.urlopen(req) as res:
    rows = json.load(res)["data"]
for row in rows:
    print(row["away"], "at", row["home"], row["side"], row["line"], row["american"], row["observed_at"])
```

4. Check `observed_at`, `age_seconds` and `stale` on every row before using a price. Full reference: https://getoddsapi.com/docs

## Use with Claude (MCP)

```bash
claude mcp add --transport http getodds https://getoddsapi.com/api/mcp \
  --header "Authorization: Bearer $GETODDS_API_KEY"
```

The MCP tools follow the key's source grants and share its allowance. Setup guide: https://getoddsapi.com/use-with-claude

## Pricing

| Plan | Allowance | Price |
| --- | --- | --- |
| Free key | 1,000 requests a day, 60 a minute | $0, no card |
| Developer Bundle | 10,000 requests or credits a month on each of four sources | $149 a month |

Free development allowance: 1,000 requests per UTC day and 60 per minute per account, with no payment step. Paid: the Developer Bundle at $149 per month, with 10,000 monthly requests or credits on each of four sources.
Details: https://getoddsapi.com/bundle

## Guide


Canonical: https://getoddsapi.com/docs/node
Reviewed: 2026-11-07 UTC

Read a small PrizePicks market sample with Node.js built-in fetch. Keep the key on your server, validate incomplete samples, and handle errors without logging credentials.

## Create a key, then make one request

Create a GetOdds account, sign in and issue a key in your dashboard. Save it as GETODDS_API_KEY in your server environment. A branded PrizePicksAPI or DraftKingsAPI key is a different credential and will not authenticate this gateway.

This example requires Node.js 22 or later and no installed SDK. Save it as read-markets.mjs, then run node read-markets.mjs. It makes one request for at most five rows; it is not a background polling job.

## A runnable server-side example

The request uses the Authorization header and a bounded timeout. Refusals and invalid responses stop the script. It prints only a fixed count summary; adapt the final comment to pass validated rows to your application.

```javascript
// Save as read-markets.mjs; run with Node.js 22 or later.
// Set GETODDS_API_KEY through your server's secret environment.
const key = process.env.GETODDS_API_KEY;
async function main() {
  if (!key) throw new Error('Set GETODDS_API_KEY before running this script.');
  let response;
  try {
    response = await fetch('https://getoddsapi.com/api/v1/markets?source=prizepicks&limit=5', {
      headers: { Authorization: 'Bearer ' + key, Accept: 'application/json' },
      signal: AbortSignal.timeout(20000), redirect: 'error',
    });
  } catch { throw new Error('Network request failed or timed out; no rows assumed.'); }
  if (!response.ok) {
    if (response.status === 429) throw new Error('Rate limited: inspect Retry-After before a bounded retry.');
    if ([401, 403].includes(response.status)) throw new Error('Check the key and source access; do not retry unchanged.');
    throw new Error('API request failed with HTTP ' + response.status);
  }
  let body;
  try { body = await response.json(); }
  catch { throw new Error('Invalid JSON; no rows assumed.'); }
  const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
  if (!object(body) || !object(body.meta) || body.meta.source !== 'prizepicks' ||
      body.meta.complete !== false || !Array.isArray(body.data) || body.data.length > 5 ||
      !body.data.every(row => object(row) && row.source === 'prizepicks' &&
        typeof row.id === 'string' && typeof row.line === 'number' &&
        Number.isFinite(row.line) && row.odds === null &&
        typeof row.observed_at === 'string' && Number.isFinite(Date.parse(row.observed_at)))) {
    throw new Error('Unexpected response contract; do not publish these rows.');
  }
  // Log a fixed summary, never headers, credentials or arbitrary error bodies.
  console.log(JSON.stringify({ source: 'prizepicks', rows: body.data.length, complete: false }));
  if (!body.data.length) console.error('Empty sample; check source availability, not invented fallback rows.');
  // Pass validated rows and their timestamps to your own server-side application here.
}
main().catch(error => { console.error(error.message); process.exitCode = 1; });

```

## Read the response as a sample

Only source=prizepicks is implemented in the gateway. The accepted limit is 1–200. The current route does not implement a league filter, cursor or historical as-of query. Do not add unsupported parameters and assume they narrowed the source.

meta.complete is false: an all-board upstream response may be cached or truncated, and the gateway returns a limited slice. observed_at is receipt time; source_updated_at may be null. odds is null because a projection threshold is not a priced sportsbook outcome.

## Build a bounded integration

Keep keys out of browser bundles, URLs and public logs. Stop on 401/403 until access is corrected. On 429, consult Retry-After and use a capped retry policy; do not immediately repeat the call. Source outages and invalid JSON require an unavailable state.

Cache appropriate server-side reads and show capture metadata. This quickstart does not promise freshness, a complete board or a particular monthly allowance. Check current dashboard usage before scheduling requests.

## Questions

### Do I need an npm odds SDK?

No. This example uses built-in fetch and AbortSignal.timeout in Node.js 22 or later.

### Can I request DraftKings through this example?

Not through the current GetOdds gateway. Use DraftKingsAPI with its separate account, key and documented endpoints.

### Does an HTTP 200 prove a complete live board?

No. Inspect the returned sample and timestamps. The gateway explicitly reports complete: false.

## Sources

- [Create a GetOdds account](https://getoddsapi.com/sign-up)
- [Create and manage a gateway key](https://getoddsapi.com/dashboard)
- [Node.js built-in fetch reference](https://nodejs.org/api/globals.html#fetch)

Independent product; not affiliated with the operators named. No picks or predicted profits.

---

GetOdds is an independent developer product and is not affiliated with any sportsbook or pick’em operator. Prices describe data, not betting advice.
