# Odds API Python quickstart: read a small board safely

A dependency-free Python example for an authenticated PrizePicks board read, plus DraftKings endpoint choices. Handle errors, keys, source timestamps and empty results explicitly.

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

## 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/python
Reviewed: 2026-11-07 UTC

A dependency-free Python example for an authenticated PrizePicks board read, plus DraftKings endpoint choices. Handle errors, keys, source timestamps and empty results explicitly.

## Start with one request, not a polling loop

Python’s standard library can read an odds or player-props API without installing an SDK. Send a bearer key in the Authorization header, set a timeout, check the response shape and retain its observation metadata. This example uses PrizePicks projection lines; it does not treat them as sportsbook prices.

## Save this as read_board.py

Create a PrizePicksAPI account, finish its setup and set PRIZEPICKS_API_KEY in the server environment. Run python3 read_board.py. The script sends one bounded request, never prints the key, and stops instead of retrying unchanged refusals. The sample task summary is sent to the service; replace it with your real purpose, excluding personal data.

```python
import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

# Complete account setup first. Never put a key in a URL or source file.
key = os.environ.get("PRIZEPICKS_API_KEY")
if not key:
    raise SystemExit("Set PRIZEPICKS_API_KEY in your server environment.")

url = "https://prizepicksapi.com/api/v1/projections?league=NBA&view=compact&limit=5"
request = Request(url, headers={
    "Authorization": "Bearer " + key,
    "Accept": "application/json",
    "X-User-Request": "Check NBA board coverage for my dashboard",
})
try:
    with urlopen(request, timeout=20) as response:
        payload = json.load(response)
except HTTPError as error:
    # Do not log headers, credentials or arbitrary response bodies.
    if error.code == 429:
        print("Rate limit reached; inspect Retry-After before retrying.", file=sys.stderr)
    elif error.code in (401, 403, 428):
        print("Check key access, plan and account setup. Do not retry unchanged.", file=sys.stderr)
    else:
        print("API request failed with HTTP", error.code, file=sys.stderr)
    raise SystemExit(1)
except (URLError, TimeoutError, json.JSONDecodeError):
    raise SystemExit("Network timeout or invalid JSON; no rows assumed.")

meta = payload.get("meta", {})
rows = payload.get("data", [])
if not isinstance(rows, list) or not all(isinstance(row, dict) for row in rows):
    raise SystemExit("Unexpected response shape; inspect the current OpenAPI contract.")
if meta.get("mode") != "live" or any(row.get("is_demo") for row in rows):
    raise SystemExit("Response is not verified live-mode data; do not publish it as live.")
print(json.dumps({"meta": meta, "rows": rows}, indent=2))
if not rows:
    print("Empty board. Check coverage and source status; do not invent lines.", file=sys.stderr)

```

## Choose an endpoint deliberately

The same HTTP pattern works for DraftKingsAPI with its own key and endpoint. Its sport parameter differs from PrizePicksAPI’s league parameter. Do not reuse credentials across hosts. Begin with each service’s coverage response and current OpenAPI contract before widening a query.

| Service | Small read | Meaning |
| --- | --- | --- |
| PrizePicksAPI | /api/v1/projections?league=NBA&view=compact&limit=5 | Player-stat projection lines |
| DraftKingsAPI | /api/v1/odds?sport=NBA&limit=5 | Stored sportsbook prices |
| DraftKingsAPI | /api/v1/pick6/projections?sport=NBA&limit=5 | Pick6 player props |

## Keep freshness and error handling visible

A live-mode response describes the API read, not a guarantee of instantaneous upstream data. Inspect meta.source_status and each row’s observed_at, keep missing values as missing, and display an empty state when no rows are present. Do not replace an empty live board with fictional demo rows.

For production, budget pagination, cache suitable reads, and apply bounded backoff to transient failures. Read Retry-After on 429 responses. Authentication, plan and setup refusals need a corrected request or account action, not an endless retry loop.

## Questions

### Does this require requests or a paid Python SDK?

No. The example uses json, os, urllib and other Python standard-library modules. API usage is still governed by the provider’s plan.

### Why is my response empty?

It can reflect filters, the current board or source health. Check coverage and timestamps before assuming a coding failure or substituting invented data.

### Can I call this directly from a public browser app?

Keep secret API keys on a server. Use a server endpoint that enforces your own access and caching policy rather than exposing the provider key in client-side code.

## Sources

- [PrizePicksAPI first authenticated call](https://prizepicksapi.com/docs/first-call)
- [PrizePicksAPI OpenAPI](https://prizepicksapi.com/openapi.json)
- [DraftKingsAPI agent reference](https://draftkingsapi.com/agents.md)
- [DraftKingsAPI OpenAPI](https://draftkingsapi.com/openapi.json)

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.
