# FanDuel Odds Scraper - Live Sports, Markets & Player Props (`abotapi/fanduel-odds-scraper`) Actor

Scrape FanDuel sportsbook odds, including moneylines, spreads, totals, player props and futures. Extract decimal, fractional and American odds, live event markers and kickoff times. Supports scheduled incremental monitoring to track odds changes.

- **URL**: https://apify.com/abotapi/fanduel-odds-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 odds records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## FanDuel Odds Scraper

FanDuel sportsbook odds for the whole board: moneylines, spreads, totals, player props, futures and Same Game Parlay markets, each price delivered in decimal, fractional and American formats. Read the featured cross-sport board, walk any sport the book offers, or paste links to specific league pages and games. Live markers, kickoff times, the site's own SGP picks, and scheduled odds monitoring in one actor.

### Why This Scraper?

- **The board, all three formats.** Every price ships as decimal, fractional and American side by side, so no conversion table is needed on your end.
- **Beyond the main board.** Alongside each event's moneyline, spread and total, the run collects the player-prop and futures markets the board carries (alternate rushing yards, championship winners, elimination stage and more) as their own rows.
- **Any sport, not just the featured mix.** Sports mode walks the board of any sport the book offers - all 19 of them, from NFL and NBA to rugby, darts and table tennis - instead of only what the featured page happens to show today.
- **Same Game Parlay surface.** The site's own SGP suggestions ship as pick rows with every leg named and priced, and an optional per-event toggle reads each game's full SGP market tab.
- **Paste a link to any page.** League pages, whole sports, or a single game's own board: paste the link, the run reads exactly that page.
- **Built for line watching.** Incremental mode keeps a per-scope baseline and emits NEW, UPDATED, REAPPEARED and EXPIRED records: odds movements are the change signal, and price history from the previous scan is retained in the baseline for movement analysis.
- **State-region aware with honest live markers.** Read any FanDuel state site (New Jersey default) with the region picker; every record carries the in-play flag and kickoff time straight from the sportsbook's own feed.

### Use Cases

- **Line shoppers:** pull FanDuel's current prices across leagues and compare against other books before placing a bet.
- **SGP builders:** pull every SGP-eligible market for one game, or watch the site's own picks with their legs.
- **Odds traders:** schedule incremental runs to watch line movement and record every move between runs.
- **Model builders:** build datasets with decimal, fractional and American prices side by side for backtesting.
- **Content pipelines:** generate previews with current prices, team names, and market narratives.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `fanduel:9c2f41ab77e31d` |
| `matchup` | `JAX Jaguars @ BAL Ravens` |
| `home` / `away` | `Baltimore Ravens` / `Jacksonville Jaguars` |
| `homeAbbr` / `awayAbbr` | `BAL` / `JAX` |
| `kickoffTime` | `2026-11-06T01:16:00.000Z` |
| `inplay` | `false` |
| `moneyline` | `{"marketName": "Moneyline", "selections": [{"name": "BAL", "odds": {"decimal": 1.83, "fractional": "100/120", "american": -120}}]}` |
| `spread` | `{"selections": [{"handicap": -9.5, "odds": {"american": -110}}]}` |
| `total` | `{"selections": [{"handicap": 44.5, "odds": {"american": -105}}]}` |
| `marketType` | `NBA_FINALS_WINNER` |
| `marketName` | `26-27 NBA Finals Winner` |
| `selections[]` | `{"name": "Oklahoma City Thunder", "odds": {"decimal": 5.5, "american": 450}}` |
| `marketId` | `734.194084044` |
| `kind` | `futures` |
| `competition` | `NBA` |
| `eventTypeId` | `7522` |
| `totalBets` | `1013` |
| `legs[]` | `{"name": "D.Henry", "marketName": "Anytime TD Scorer", "odds": {"american": -150}}` |
| `homeLogo` / `awayLogo` | `https://assets.sportsbook.fanduel.com/images/team/nfl/...` |
| `selections[].odds.decimal` | `1.83` |
| `selections[].odds.american` | `-120` |
| `oddsTimestamp` | `2026-11-04T05:00:00+00:00` |
| `changeType` / `changedFields` | `UPDATED` / `["moneyline"]` |
| `firstSeenAt` / `lastSeenAt` | `2026-11-01T...` / `2026-11-04T...` |

### How to Use

The run reads FanDuel's own public odds feeds directly (no login, no browser): pick a mode and a region, set Max items, run.

- **popular** covers the site's featured cross-sport board: each event ships with its moneyline, spread and total, every prop/futures market ships as its own row, and the site's own Same Game Parlay picks ship with their legs.
- **sports** walks the board of every sport you pick - the league page the site maintains for the major US sports, the whole-sport page for the rest.
- **urls** reads exactly the pages you paste: league pages, whole sports, or one game's own board.

#### Run it from your code

**Python:**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("abotapi/fanduel-odds-scraper").call(run_input={
    "mode": "popular",
    "region": "NJ",
    "maxItems": 20,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**JavaScript:**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('abotapi/fanduel-odds-scraper').call({
    mode: 'popular',
    region: 'NJ',
    maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**Scheduled line monitoring:** turn on incremental mode with a `stateKey`; every run pushes only NEW, UPDATED, REAPPEARED or EXPIRED records.

#### Input examples

**Default board snapshot:**

```json
{ "mode": "popular", "region": "NJ", "maxItems": 20 }
```

**Scheduled incremental monitoring:**

```json
{ "mode": "popular", "region": "NJ", "incrementalMode": true, "stateKey": "nj-board", "maxItems": 200 }
```

**Walk two whole sports:**

```json
{ "mode": "sports", "sports": ["American Football", "Soccer"], "region": "NJ", "maxItems": 200 }
```

**One game's own board, with its SGP markets:**

```json
{ "mode": "urls", "urls": ["https://sportsbook.fanduel.com/football/nfl/team-a-@-team-b-36244870"], "fetchSgpMarkets": true, "maxItems": 100 }
```

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | Select | `popular` | `popular` = the featured cross-sport board; `sports` = walk whole sports; `urls` = read the pages you paste. |
| `sports` | Multi-select | NFL, NBA | sports mode: which sports to walk (all 19 the book offers). |
| `urls` | List | three example links | links mode: FanDuel league pages, whole-sport pages, or single-game links. |
| `region` | Select | `NJ` | Which FanDuel state site to read; odds and availability vary by state. |
| `liveOnly` | Boolean | `false` | Keep only records whose game is flagged in-play on the pages read this run. |
| `fetchSgpMarkets` | Boolean | `false` | Also read each collected event's Same Game Parlay market tab; each event whose SGP markets are delivered counts as one enrichment read. |
| `maxItems` | Integer | `20` | Stop after this many odds records (boards, markets, picks and SGP tabs all count). Leave 0 for no limit. |
| `proxy` | Proxy | Residential US | Residential US exits are recommended for the most reliable results. |
| `resumeFromRunId` | String | (empty) | Continue a previous run: records that run already delivered are skipped. |
| `incrementalMode` | Boolean | `false` | Keep a per-scope baseline; emit NEW, UPDATED, REAPPEARED, EXPIRED records. |
| `stateKey` | String | (empty) | Optional name for the incremental baseline; separate keys per scope. |
| `emitUnchanged` | Boolean | `false` | Incremental mode: also push records whose odds did not move. |
| `emitExpired` | Boolean | `false` | Incremental mode: after a complete scan, push rows for records no longer present. |
| `ignoreFieldsForChanges` | List | (empty) | Extra fields that must never mark a row UPDATED. |
| `mcpConnectors` | List | (empty) | Apify MCP connector IDs to notify with the first collected rows. Leave empty to skip. |
| `maxNotifyListings` | Integer | `10` | Internal: how many of the first collected rows a connector export may send. |
| `notionParentPageUrl` | String | (empty) | Internal: target page for a Notion connector export. |

### Output Example

The dataset carries three record kinds behind the union view: event boards (moneyline/spread/total flattened per game), markets (props, futures, and - with the toggle on - each event's SGP market tab), and the site's own Same Game Parlay picks with their legs. The Output tabs split them.

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": "fanduel:9c2f41ab77e31d",
  "matchup": "JAX Jaguars @ BAL Ravens",
  "home": "Baltimore Ravens",
  "away": "Jacksonville Jaguars",
  "kickoffTime": "2026-11-06T01:16:00.000Z",
  "inplay": false,
  "moneyline": {
    "marketName": "Moneyline",
    "selections": [
      { "name": "Baltimore Ravens", "odds": { "decimal": 1.83, "fractional": "100/120", "american": -120 } },
      { "name": "Jacksonville Jaguars", "odds": { "decimal": 1.98, "fractional": "100/102", "american": -102 } }
    ]
  },
  "oddsTimestamp": "2026-11-04T05:00:00+00:00"
}
```

### Send results into your apps (MCP connectors)

Give an MCP connector ID in `mcpConnectors` and the run notifies your connected app (Notion, Sheets and more) with the first collected rows, capped by the internal `maxNotifyListings` setting. No connector configured means the run skips the export step entirely.

### Plan Requirement

This actor is billed per event: a start charge per run, a charge for each odds record delivered, and - with the SGP toggle on - one enrichment charge per event whose SGP market tab is delivered. A paid Apify plan is recommended; residential US proxy usage rides your plan's residential allowance.

### FAQ

#### How much does a run cost?

The start event bills once per run and each delivered record bills once. The run reads the sportsbook's public feeds, so larger Max items barely increase cost: schedule fewer, larger runs for the lowest per-record price. Your run's Pricing tab shows live per-event prices for your plan.

#### Is it legal to scrape FanDuel?

The actor reads only the publicly displayed odds feeds of FanDuel's state sportsbooks, without logging in and without taking account-held data. You remain responsible for complying with FanDuel's terms and your local regulations; gambling availability depends on your jurisdiction.

#### Can I monitor line movement on a schedule?

Yes. Enable incremental mode with a `stateKey` and schedule runs: each run diffs against the previous scan and pushes only NEW, UPDATED, REAPPEARED or EXPIRED records. Odds movements are the change signal.

#### Why are some sports missing from my run?

In the default mode the run reads the site's featured cross-sport board, whose mix rotates with the sports calendar. Switch to sports mode and pick the sports you want to walk their whole boards regardless of what is featured today. A sport with no live or upcoming events serves an empty board - out-of-season sports simply have nothing to price.

#### Does this cover live (in-play) games?

The actor marks every record with the in-play flag and kickoff time the feed carries, and a Live games only filter keeps records whose game is running right now. The book's dedicated live page itself is not anonymously readable, so the filter works over the pages the run reads rather than walking a live-only feed.

#### What does the SGP toggle cost?

Each event whose Same Game Parlay market tab is read and delivered counts as one enrichment charge on top of the per-record charge; events whose tab cannot be read, runs with the toggle off, and events suppressed as unchanged are never charged. Your run's Pricing tab shows the live per-event prices for your plan.

#### Why did my run fail with "the site refused every connection"?

The sportsbook's edge can refuse connections in waves. The actor rides residential exits and retries, but a full wave fails honestly; re-run shortly.

#### Can I use this with AI agents and MCP?

Yes, via the Apify MCP server: add `abotapi/fanduel-odds-scraper` and ask for odds naturally. Every field is plain JSON.

### 🔗 Want more sports data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>⚽ <a href="https://apify.com/abotapi/bet365-odds-scraper"><b>bet365 Odds Scraper</b></a><br>Scrape bet365 live in-play and pre-match odds across 20+ sports. Extract fixture...</td><td>⚽ <a href="https://apify.com/abotapi/sportsbook-odds-scraper"><b>Sportsbook Odds Scraper (1xBet, Melbet, Linebet, Paripulse)</b></a><br>Collect live and prematch betting odds from 1xBet, Melbet, Linebet and Paripulse. Give it...</td></tr>
<tr><td>⚽ <a href="https://apify.com/abotapi/sofascore-scraper"><b>SofaScore Scraper ⚽ Live Scores, Stats, Players &amp; Odds</b></a><br>Pull structured sports data from SofaScore across football, basketball, tennis, and 20+...</td><td>⚽ <a href="https://apify.com/abotapi/fotmob-scraper"><b>FotMob Scraper ⚽ Match Details, Stats, Lineups &amp; Players</b></a><br>Pull structured football data from FotMob: match details with scores, team stats, events...</td></tr>
<tr><td>⚽ <a href="https://apify.com/abotapi/globo-ge"><b>Globo Esporte Scraper</b></a><br>Scrape public sports content from ge.globo.com, including news, videos, matches and feed...</td><td>⚽ <a href="https://apify.com/abotapi/hotstar-com-scraper"><b>JioHotstar Scraper</b></a><br>Scrape the JioHotstar catalog by content type or URL. Extract shows, movies, episodes...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/fanduel-odds-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

popular = the site's own cross-sport featured board: every featured event with moneyline, spread and total, plus the player-prop and futures markets the board carries. sports = walk one or more whole sports (pick below). urls = read specific pages you paste (league pages, whole sports, or a single game).

## `sports` (type: `array`):

sports mode: which sports to walk. Each sport reads its richest board (the league page the site maintains for the major US sports, the whole-sport page for the rest).

## `urls` (type: `array`):

links mode: paste FanDuel page links. Supported: league pages (sportsbook.fanduel.com/navigation/nfl), whole sports (sportsbook.fanduel.com/soccer), and a single game (the link ends in the game's id). Anything else is skipped with a warning.

## `region` (type: `string`):

Which FanDuel state site to read. Odds and availability vary by state.

## `liveOnly` (type: `boolean`):

Keep only records whose game is flagged in-play on the pages read this run. The live board itself is not anonymously readable, so this filters the pages above rather than walking a live-only feed.

## `fetchSgpMarkets` (type: `boolean`):

Also read each collected event's Same Game Parlay market tab (its full SGP-eligible market set, priced like every other market). Each event whose SGP markets are delivered counts as one enrichment read; events whose tab cannot be read are not counted. Raise Max items to leave room for the extra rows.

## `maxItems` (type: `integer`):

Stop after this many odds records (event boards, markets, the site's own Same Game Parlay picks, and SGP market tabs all count). Leave 0 for no limit.

## `proxy` (type: `object`):

Residential US exits are recommended for the most reliable results.

## `resumeFromRunId` (type: `string`):

Continue a previous run: records that run already delivered are skipped.

## `incrementalMode` (type: `boolean`):

Scheduled monitoring: keep a per-scope baseline and emit NEW, UPDATED, REAPPEARED and EXPIRED records. Odds movements are the change signal.

## `stateKey` (type: `string`):

Optional name for the incremental baseline.

## `emitUnchanged` (type: `boolean`):

Incremental mode: also push records whose odds did not move.

## `emitExpired` (type: `boolean`):

Incremental mode: after a complete scan, push rows for records no longer present.

## `ignoreFieldsForChanges` (type: `array`):

Additional record fields that must never mark a row UPDATED.

## `mcpConnectors` (type: `array`):

Apify MCP connector IDs to notify with the first collected rows. Leave empty to skip.

## `maxNotifyListings` (type: `integer`):

Internal: how many of the first collected rows a connector export may send.

## `notionParentPageUrl` (type: `string`):

Internal: target page for a Notion connector export.

## Actor input object example

```json
{
  "mode": "popular",
  "sports": [
    "American Football",
    "Basketball"
  ],
  "urls": [
    "https://sportsbook.fanduel.com/navigation/nfl",
    "https://sportsbook.fanduel.com/soccer",
    "https://sportsbook.fanduel.com/football/nfl/jacksonville-jaguars-@-baltimore-ravens-36244870"
  ],
  "region": "NJ",
  "liveOnly": false,
  "fetchSgpMarkets": false,
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 10
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `markets` (type: `string`):

No description

## `picks` (type: `string`):

No description

## `changes` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "popular",
    "sports": [
        "American Football",
        "Basketball"
    ],
    "urls": [
        "https://sportsbook.fanduel.com/navigation/nfl",
        "https://sportsbook.fanduel.com/soccer",
        "https://sportsbook.fanduel.com/football/nfl/jacksonville-jaguars-@-baltimore-ravens-36244870"
    ],
    "region": "NJ",
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/fanduel-odds-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "popular",
    "sports": [
        "American Football",
        "Basketball",
    ],
    "urls": [
        "https://sportsbook.fanduel.com/navigation/nfl",
        "https://sportsbook.fanduel.com/soccer",
        "https://sportsbook.fanduel.com/football/nfl/jacksonville-jaguars-@-baltimore-ravens-36244870",
    ],
    "region": "NJ",
    "maxItems": 20,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/fanduel-odds-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "popular",
  "sports": [
    "American Football",
    "Basketball"
  ],
  "urls": [
    "https://sportsbook.fanduel.com/navigation/nfl",
    "https://sportsbook.fanduel.com/soccer",
    "https://sportsbook.fanduel.com/football/nfl/jacksonville-jaguars-@-baltimore-ravens-36244870"
  ],
  "region": "NJ",
  "maxItems": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call abotapi/fanduel-odds-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/fanduel-odds-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/0biVkQh3pWBr0F8ha/builds/7DlbbCoTEc7JpJpqX/openapi.json
