# FanDuel API errors, rate limits and retries

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/fanduel-api-errors (this is its markdown version, https://fanduelapi.com/fanduel-api-errors.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/)/ Errors and limits

Errors and limits

# FanDuel API errors and rate limits

Every error is JSON with a stable code and a message that says what to change. Retry 429 RATE\_LIMITED and 503 after Retry-After; stop on QUOTA\_EXCEEDED; fix the request on any other 4xx.

[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.

## Every code

| HTTP | Code                 | Means                                                                                 | Do this                                                                         |
| ---- | -------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| 400  | INVALID\_QUERY       | A parameter is wrong: an unknown sport or league, a bad date, a bad status.           | Fix the request. The message names the parameter and what it accepts.           |
| 400  | INVALID\_CURSOR      | since, cursor or Last-Event-ID is not a cursor this endpoint issued.                  | Use next\_cursor or X-Next-Cursor exactly as returned, or since=now to restart. |
| 401  | INVALID\_API\_KEY    | The Authorization header is missing, mistyped or revoked.                             | Send Authorization: Bearer <key>. Create a new key if this one was revoked.     |
| 402  | PLAN\_REQUIRED       | The endpoint is not in your plan: exports below Developer, webhooks below Production. | The body names the plan that includes it and its upgrade\_url.                  |
| 402  | ARCHIVE\_REQUIRED    | An export reaches further back than your plan's included history.                     | Narrow the range, or add a full archive from the dashboard.                     |
| 403  | ACCOUNT\_SUSPENDED   | The account behind the key is suspended.                                              | Contact support@fanduelapi.com.                                                 |
| 404  | NOT\_FOUND           | No prop, market, game or slate has that id.                                           | Take ids from a list endpoint; a prop removed long ago may be gone.             |
| 429  | RATE\_LIMITED        | More requests this minute than the plan allows.                                       | Wait for Retry-After seconds, then retry.                                       |
| 429  | QUOTA\_EXCEEDED      | The monthly allowance is used up.                                                     | Stop. It resets on the 1st (UTC); the body says when and offers the plan above. |
| 429  | STREAM\_LIMIT        | Every stream connection the plan allows is already open.                              | Close one, or wait about a minute for a dropped one to expire.                  |
| 503  | SERVICE\_UNAVAILABLE | A temporary problem on our side.                                                      | Retry after Retry-After seconds (5). Quote X-Request-ID to support.             |

## What a 429 says

A limit response names the plan, which limit was hit (`limit_kind`), when it resets (`reset_at`), one sentence to show your user, and the plan above with its link. Agents can relay `user_message` as is. A monthly limit adds a one-time top-up link.

A 429 on Developer, as the API builds it

```
HTTP/1.1 429 Too Many Requests
Retry-After: 21

{
  "error": {
    "code": "RATE_LIMITED",
    "plan": "developer",
    "limit": 60,
    "reset_at": "2026-11-06T01:13:00.000Z",
    "plan_details": {
      "id": "developer",
      "name": "Developer",
      "price_usd_month": 49,
      "requests_per_month": 10000,
      "requests_per_minute": 60
    },
    "limit_kind": "requests_per_minute",
    "notify_user": false,
    "user_message": "FanDuel API Developer allows 60 requests a minute (bursts up to 180 in any 3 minutes), so I am waiting 1 seconds before the next request.",
    "upgrade_url": "https://fanduelapi.com/upgrade?plan=production&source=rest",
    "upgrade_plan": {
      "id": "production",
      "name": "Production",
      "price_usd_month": 149,
      "requests_per_month": 50000,
      "requests_per_minute": 120
    },
    "fewer_calls": {
      "odds_board": "https://fanduelapi.com/api/v1/odds?sport=nfl",
      "props_board": "https://fanduelapi.com/api/v1/props?sport=nfl&view=compact&limit=1000",
      "changes": "https://fanduelapi.com/api/v1/changes?since=now",
      "advice": "Read every game of a league in one call with /api/v1/odds?sport= (game lines) or /api/v1/props?view=compact (player props), then poll /api/v1/changes with next_cursor for only what moved. Send If-None-Match with the last ETag: an unchanged answer is a 304 that costs no credits."
    }
  }
}
```

## A retry loop that respects both limits

Python

```
import os, random, time, requests

S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['FANDUEL_API_KEY']}"

def get(path, **params):
    for attempt in range(5):
        r = S.get(f"https://fanduelapi.com/api/v1{path}", params=params, timeout=20)
        if r.status_code < 400:
            return r.json()
        err = r.json().get("error", {})
        if err.get("code") == "QUOTA_EXCEEDED":
            raise SystemExit(err.get("user_message") or err.get("message"))  # waiting will not help
        if r.status_code in (429, 503):
            time.sleep(int(r.headers.get("Retry-After", "5")) + random.random())
            continue
        raise RuntimeError(f"{r.status_code} {err.get('code')}: {err.get('message')}")  # 4xx: fix the request
    raise RuntimeError("gave up after 5 tries")
```

Spend fewer credits in the first place: use the largest page size (see [pagination](https://fanduelapi.com/fanduel-api-pagination)) and follow the change feed instead of re-reading the board. The quota per plan is on the [free plan](https://fanduelapi.com/fanduel-api-free) page and on [pricing](https://fanduelapi.com/pricing); the 401 case is covered on [API keys](https://fanduelapi.com/fanduel-api-key), and a Node worker with the same rules is on [the Node.js page](https://fanduelapi.com/fanduel-api-node).

## Questions

## Related

- [API key How to get a FanDuel API key in a minute, keep it out of code, check it works with one request, rotate it and read a 401 when it is wrong.](https://fanduelapi.com/fanduel-api-key)
- [Free plan What the free FanDuel API plan includes: every endpoint on data five minutes behind, the monthly and per-minute limits, and what needs a paid plan.](https://fanduelapi.com/fanduel-api-free)
- [Pagination How FanDuel API responses page: limit and offset with has\_more on lists, next\_cursor on the change feed and exports, and the limits per endpoint.](https://fanduelapi.com/fanduel-api-pagination)
- [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)
- [Pricing Free on delayed data, then real time from the Developer plan.](https://fanduelapi.com/pricing)

## 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.

[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.
