# Build a FanDuel odds screen: the data workflow

FanDuel API (https://fanduelapi.com) is an independent API for FanDuel Sportsbook data: every player prop and game market FanDuel lists in NBA, NFL, NHL, MLB, college basketball and college football, normalized per player and stat with over and under prices in American and decimal odds, plus every line and price move with its timestamp, over REST, webhooks and MCP with one key. FanDuel API is an independent data service operated by FF API Inc. It is not affiliated with, endorsed by or sponsored by FanDuel.

Source page: https://fanduelapi.com/docs/odds-screen (this is its markdown version, https://fanduelapi.com/docs/odds-screen.md).

## Free offer

- Free plan: 100 credits a month, data delayed 5 minutes, no card. REST and MCP on every plan.
- Real-time data starts at Developer, $49 a month for 10,000 credits.
- Get a free key: https://fanduelapi.com/sign-up?ref=ai-claudebot

## Quickstart

1. Create a free account at https://fanduelapi.com/sign-up?ref=ai-claudebot and create an API key in the dashboard (https://fanduelapi.com/dashboard/keys). Keys start with `fd_`. Store it as `FANDUEL_API_KEY`.
2. Make a first call with curl:

```bash
curl "https://fanduelapi.com/api/v1/props?sport=nba&stat=points&limit=5" \
  -H "Authorization: Bearer $FANDUEL_API_KEY"
```

3. Or with Python (`pip install requests`):

```python
import os
import time
from datetime import datetime, timezone

import requests

BASE = "https://fanduelapi.com/api/v1"
SESSION = requests.Session()
SESSION.headers["Authorization"] = f"Bearer {os.environ['FANDUEL_API_KEY']}"


def get(path, **params):
    """GET one endpoint and return its data list, waiting out rate limits."""
    while True:
        r = SESSION.get(f"{BASE}{path}", params=params, timeout=10)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        r.raise_for_status()
        return r.json()["data"]


def price(odds):
    """American odds as text. A suspended market can come back with no price."""
    return "off" if odds is None else f"{odds:+d}"

props = get("/props", sport="nba", stat="points")
for p in props:
    print(f"{p['player']:<26} {str(p['line']):>5}  over {price(p['over_american']):>5}  under {price(p['under_american']):>5}")
```

4. Stay current with `GET /api/v1/changes?since=<cursor>` instead of re-reading every prop. Full reference: https://fanduelapi.com/openapi.json and https://fanduelapi.com/llms-full.txt.

## Use it from Claude (MCP)

Claude Code, with your key read from the shell:

```bash
claude mcp add --transport http --scope user fanduelapi https://fanduelapi.com/api/mcp \
  --header "Authorization: Bearer $FANDUEL_API_KEY"
```

Claude on the web or desktop: Settings, Connectors, Add custom connector, then paste https://fanduelapi.com/api/mcp. Without a key it gives the keyless tools (search_docs, fetch_doc, get_sample_props). Setup for other agents: https://fanduelapi.com/mcp.

## Pricing

| Plan | Price | Credits a month | Calls a minute | Includes |
| --- | --- | --- | --- | --- |
| Free | Free | 100 | 20 | REST and MCP access, Data delayed 5 minutes |
| Developer | $49/month | 10,000 | 60 | Real-time data, Line move history, Change stream |
| Production | $149/month | 50,000 | 120 | Real-time data, Line move history, Webhooks, Priority support |
| Scale | $499/month | 250,000 | 180 | Real-time data, Full line move history, Webhooks, Bulk export, Priority support |

Cancel paid plans any time from the billing portal. Details: https://fanduelapi.com/pricing

## This page

[FanDuel API](https://fanduelapi.com/)/[Docs](https://fanduelapi.com/docs) / Odds screen

Docs workflow

# Build a FanDuel odds screen

An odds screen is a board that stays current: load every prop once, then apply only what changes. Here are the calls, the fields to show, how to work out hold and fair prices, and how often each plan can refresh.

[Get a free API key](https://fanduelapi.com/sign-up)[Read the docs](https://fanduelapi.com/docs)

FanDuel API is an independent data service operated by FF API Inc. It is not affiliated with, endorsed by or sponsored by FanDuel.

## The calls, in order

1. `GET /api/v1/changes?since=now`: take the cursor first, before anything moves.
2. `GET /api/v1/events?league=NBA`: the games, with how many props each has, to build the game list.
3. `GET /api/v1/props?league=NBA&limit=200&offset=N`: the board, paged until `has_more` is false.
4. `GET /api/v1/changes?since=<cursor>&league=NBA`: on a timer, apply each row's `current` to the prop, drop removed ones, keep the new cursor.

## A terminal screen in Python

screen.py

```
import os, time, requests

API = "https://fanduelapi.com/api/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['FANDUEL_API_KEY']}"

def implied(a):
    return None if a is None else (-a / (-a + 100) if a < 0 else 100 / (a + 100))

def load(league):
    board, offset = {}, 0
    while True:
        body = S.get(f"{API}/props", params={"league": league, "limit": 200, "offset": offset}, timeout=20).json()
        board.update({p["id"]: p for p in body["data"]})
        if not body["meta"]["has_more"]:
            return board
        offset += 200

def render(board):
    rows = sorted(board.values(), key=lambda p: (p["starts_at"], p["event_name"], p["player"], p["stat"]))
    for p in rows:
        o, u = implied(p["over_american"]), implied(p["under_american"])
        hold = f"{(o + u - 1) * 100:4.1f}%" if o and u else "  off"
        moved = (p.get("move") or {}).get("line_move") or 0
        print(f"{p['player']:<24} {p['stat']:<14} {p['line']!s:>6} {p['over_american']!s:>6} {p['under_american']!s:>6} {hold} {moved:+}")

league = "NBA"
cursor = S.get(f"{API}/changes", params={"since": "now"}, timeout=20).json()["meta"]["next_cursor"]
board = load(league)
render(board)
while True:
    time.sleep(300)
    body = S.get(f"{API}/changes", params={"since": cursor, "league": league, "limit": 500}, timeout=20).json()
    for c in body["data"]:
        if c["kind"] == "removed":
            board.pop(c["prop_id"], None)
        elif c["prop_id"] in board:
            board[c["prop_id"]].update(c["current"])
        # a "new" prop has no event fields here; the next full load() adds it
    cursor = body["meta"]["next_cursor"]
    if body["data"]:
        render(board)
```

Hold is the two implied probabilities added, minus one: the lower it is, the cheaper the market. The no-vig fair price divides each implied probability by their sum; the [props against the fair price](https://fanduelapi.com/compare) page shows both for the live board, and the [odds tools](https://fanduelapi.com/tools) do it for any price.

## How often each plan can refresh

- **Free:** 100 credits a month on data five minutes behind. Load the board and refresh by hand while you build.
- **Developer:** 10,000 a month; one league polled every 5 minutes.
- **Production:** 50,000 a month; every 60 seconds, or skip polling with webhooks.

For a screen that updates the moment a line moves, keep a connection on the [stream](https://fanduelapi.com/fanduel-api-websocket). A Node version of the same cache is on [the Node.js page](https://fanduelapi.com/fanduel-api-node), and a runnable odds board is among the [examples](https://fanduelapi.com/examples).

## Questions

## Related

- [Examples Runnable examples on FanDuel data: an odds screen, an alert when a prop moves, closing line value, a hold scanner and DFS salary against prop lines.](https://fanduelapi.com/examples)
- [Node.js worker A Node.js worker that loads the FanDuel props board once, then applies the change feed with a saved cursor, so a local copy stays current cheaply.](https://fanduelapi.com/fanduel-api-node)
- [Props vs fair price FanDuel over and under prices next to the no-vig fair price for the same prop, with the hold on each market, five minutes behind.](https://fanduelapi.com/compare)
- [Schema glossary Every field the FanDuel API returns, defined: props, move summaries, history points, changes, markets and selections, results, DFS players and meta.](https://fanduelapi.com/docs/schema-glossary)

## Make your first call in a few minutes

Sign up, create a key in the dashboard, and send it as a Bearer token. The free plan needs no card and reads the same data five minutes behind.

[Get a free API key](https://fanduelapi.com/sign-up)

## FAQ

### Is there a free plan?

Yes. Free includes API access with delayed data, enough to build and test a model.

### Is this affiliated with FanDuel?

No. This is an independent data service operated by FF API Inc. and is not affiliated with or endorsed by FanDuel.

### Which sports are covered?

NBA, NFL, NHL, MLB and college sports as FanDuel lists them, starting with player props.

### How do I cancel?

From the billing portal in your dashboard, at any time.

### How do I get a free key?

Sign up at https://fanduelapi.com/sign-up?ref=ai-claudebot. Free plan: 100 credits a month, data delayed 5 minutes, no card. REST and MCP on every plan.

### Can an AI agent sign up for me?

Yes. The agent sends POST https://fanduelapi.com/api/v1/agent/signup with your email and what you are building (add "ref": "ai-claudebot"), you approve the emailed link, and the agent collects the key. Details: https://fanduelapi.com/connect.
