# Sports Betting Odds Scraper — DraftKings Lines via ESPN (`fabrikit/sports-odds-scraper`) Actor

Sports betting odds scraper: one row per game and sportsbook with spread, total and moneyline (American + decimal), opening and closing lines, favorite and final result. NFL, NBA, MLB, NHL, college, MLS, Premier League and other soccer leagues, UFC.

- **URL**: https://apify.com/fabrikit/sports-odds-scraper.md
- **Developed by:** [Fabrikit](https://apify.com/fabrikit) (community)
- **Categories:** Other, AI, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.95 / 1,000 odds row scrapeds

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

## Sports Betting Odds Scraper

Sports Betting Odds Scraper extracts sportsbook lines per game from ESPN's public betting feed (the DraftKings odds shown on espn.com): point spread, total (over/under) and moneyline for NFL, NBA, MLB, NHL, college football and basketball, MLS, the Premier League, LaLiga, Bundesliga, Serie A, Ligue 1, Champions League, Liga MX and UFC fights. Every row is one game x one sportsbook, flat and typed: current, opening and closing lines in American and decimal odds, the favourite, kick-off time, teams and records, live or final scores, and for finished games whether the favourite covered and whether the total went over. Plain HTTP against ESPN's JSON API: no browser, no login, no proxy, no DraftKings scraping (their site blocks bots; ESPN republishes the lines).

### What you get

| Field | Example |
|---|---|
| `league`, `leagueName`, `sport`, `season`, `seasonType`, `week` | nfl, National Football League, football, 2026, Regular Season, 7 |
| `eventId`, `competitionId`, `eventName`, `shortName`, `startTime`, `espnUrl` | 401873010, New England Patriots at Chicago Bears, NE @ CHI, 2026-10-23T00:15:00.000Z |
| `status`, `statusDetail`, `completed` | pre / in / post, Scheduled / Final / Final/OT |
| `homeTeam`, `homeTeamAbbr`, `homeTeamId`, `homeRecord`, `awayTeam`, … | Chicago Bears, CHI, 3, 4-2 (fighters for UFC) |
| `homeScore`, `awayScore`, `winner` | 121, 113, home (null before the game) |
| `venue`, `venueCity`, `venueCountry`, `neutralSite`, `broadcast` | Soldier Field, Chicago, USA, false, Prime Video |
| `provider`, `providerId`, `details`, `favorite`, `favoriteTeam` | DraftKings, 100, "CHI -3", home, Chicago Bears |
| `homeSpread`, `awaySpread`, `homeSpreadOdds`, `awaySpreadOdds` (+ `…Decimal`) | -3, 3, -102, -118 (run line for MLB, puck line for NHL, goal line for soccer) |
| `total`, `overOdds`, `underOdds` (+ `…Decimal`) | 50.5, -115, -105 (rounds for UFC) |
| `homeMoneyline`, `awayMoneyline`, `drawMoneyline` (+ `…Decimal`) | -155, +130, +250 (draw only for soccer) |
| `openHomeSpread`, `openHomeSpreadOdds`, `openAwaySpreadOdds`, `openTotal`, `openOverOdds`, `openUnderOdds`, `openHomeMoneyline`, `openAwayMoneyline` | the line when the book opened it |
| `closeHomeSpread`, `closeTotal`, `closeHomeMoneyline`, `closeAwayMoneyline`, … | closing line of finished games (null while the game is upcoming) |
| `spreadResult`, `totalResult`, `moneylineResult` | home / away / push, over / under / push, home / away / draw |
| `oddsSource`, `scrapedAt` | core (ESPN odds API) or scoreboard (embedded line) |

Sportsbooks: ESPN carries DraftKings for every league and adds Bet 365 (fractional 1X2 and goal line) on some European soccer matches. You get every book ESPN returns for the game; `providers` filters them.

### Use cases

- **Line shopping and alerts**: schedule the Actor every hour for the leagues you follow and diff `homeSpread` / `total` / moneylines against the previous run.
- **Closing line value (CLV)**: run with `gameStates: ["post"]` the morning after; `closeHomeSpread`, `closeTotal` and the closing moneylines are there together with the scores and `spreadResult` / `totalResult`.
- **Models and backtests**: build a daily table of opening vs. closing lines with results for NFL, NBA, MLB, NHL, soccer or UFC without touching a sportsbook site.
- **Content and apps**: feed odds widgets, newsletters, Discord bots or AI agents with a clean JSON of today's games and their prices.

### Input

```json
{
    "leagues": ["nfl", "nba", "epl"],
    "customLeagues": ["soccer/ned.1"],
    "dateFrom": "2026-10-24",
    "dateTo": "2026-10-26",
    "gameStates": ["pre"],
    "providers": ["DraftKings"],
    "oddsSource": "core",
    "includeEventsWithoutOdds": false,
    "maxItems": 500
}
```

- `leagues`: pick from the list (nfl, ncaaf, nba, ncaab, mlb, nhl, mls, epl, laliga, bundesliga, seriea, ligue1, ucl, ligamx, ufc). `customLeagues` takes any other ESPN scoreboard as `sport/league` (soccer/por.1, soccer/uefa.europa, basketball/wnba…); lines exist where DraftKings prices the league.
- Dates are US Eastern game days. With no dates the Actor covers today plus `days` - 1 more days (default 3); NFL and college football without dates use ESPN's current week instead, so a Tuesday run already returns the coming weekend. `dateFrom` also accepts `today`, `tomorrow` or `+2`. Up to 31 days per run.
- `gameStates`: `pre` (upcoming), `in` (live), `post` (finished). Empty = all three.
- `oddsSource`: `core` (default) makes one request per game to ESPN's odds API and returns every sportsbook with opening, current and closing lines, also for finished games. `scoreboard` uses only the DraftKings line embedded in the scoreboard: no extra requests, upcoming games only.
- `includeEventsWithoutOdds`: save (and pay for) a row with empty odds fields for games that have no line yet, e.g. to get the schedule. Off by default.
- `maxItems` is exact and global; a game x book pair is saved and charged once even if it appears on two days.

### Output example

One row from the default input (NFL week 7, DraftKings):

```json
{
    "eventId": "401873010",
    "competitionId": "401873010",
    "league": "nfl",
    "leagueName": "National Football League",
    "leagueAbbr": "NFL",
    "sport": "football",
    "season": 2026,
    "seasonType": "Regular Season",
    "week": 7,
    "eventName": "New England Patriots at Chicago Bears",
    "shortName": "NE @ CHI",
    "competitionName": null,
    "startTime": "2026-10-23T00:15:00.000Z",
    "status": "pre",
    "statusDetail": "Scheduled",
    "completed": false,
    "neutralSite": false,
    "homeTeam": "Chicago Bears",
    "homeTeamAbbr": "CHI",
    "homeTeamId": "3",
    "homeRecord": "4-2",
    "awayTeam": "New England Patriots",
    "awayTeamAbbr": "NE",
    "awayTeamId": "17",
    "awayRecord": "4-2",
    "homeScore": null,
    "awayScore": null,
    "winner": null,
    "venue": "Soldier Field",
    "venueCity": "Chicago",
    "venueCountry": "USA",
    "broadcast": "Prime Video",
    "espnUrl": "https://www.espn.com/nfl/game/_/gameId/401873010/patriots-bears",
    "provider": "DraftKings",
    "providerId": "100",
    "details": "CHI -3",
    "favorite": "home",
    "favoriteTeam": "Chicago Bears",
    "homeSpread": -3,
    "awaySpread": 3,
    "homeSpreadOdds": -102,
    "awaySpreadOdds": -118,
    "homeSpreadOddsDecimal": 1.98,
    "awaySpreadOddsDecimal": 1.84,
    "total": 50.5,
    "overOdds": -115,
    "underOdds": -105,
    "overOddsDecimal": 1.86,
    "underOddsDecimal": 1.95,
    "homeMoneyline": -155,
    "awayMoneyline": 130,
    "drawMoneyline": null,
    "homeMoneylineDecimal": 1.64,
    "awayMoneylineDecimal": 2.3,
    "drawMoneylineDecimal": null,
    "openHomeSpread": -1.5,
    "openHomeSpreadOdds": -105,
    "openAwaySpreadOdds": -115,
    "openTotal": 47.5,
    "openOverOdds": -110,
    "openUnderOdds": -110,
    "openHomeMoneyline": -120,
    "openAwayMoneyline": 100,
    "closeHomeSpread": null,
    "closeHomeSpreadOdds": null,
    "closeAwaySpreadOdds": null,
    "closeTotal": null,
    "closeOverOdds": null,
    "closeUnderOdds": null,
    "closeHomeMoneyline": null,
    "closeAwayMoneyline": null,
    "spreadResult": null,
    "totalResult": null,
    "moneylineResult": null,
    "oddsSource": "core",
    "scrapedAt": "2026-10-21T12:40:02.118Z"
}
```

For a finished game the same row carries `homeScore` / `awayScore`, `winner`, the `close…` lines and `spreadResult` / `totalResult` / `moneylineResult`. A `SUMMARY` record in the key-value store lists games found, games with lines and rows saved per league, the requests made and any ESPN error.

### Pricing

Pay per event: one `odds-row` event per row saved. See the Pricing tab for current prices; paid Apify plans get lower rates. The Actor makes one small JSON request per game, no browser, no proxy.

Games without a line are not charged unless you turn `includeEventsWithoutOdds` on.

### How it compares

- **Scraping DraftKings, FanDuel or other sportsbooks directly**: their sites sit behind Akamai / Cloudflare and need residential proxies or browsers, so those Actors break often. ESPN republishes the DraftKings line for every major league in a stable JSON API, with opening and closing numbers you do not get from a live sportsbook page.
- **Odds aggregators' APIs**: they cover more books. This Actor is pay-per-event and includes scores and results in the same row.
- **ESPN score scrapers**: they return games and scores; this Actor is built around the odds: every sportsbook ESPN lists, open / current / close, favourite, results and decimal odds, one flat row per book.

What it does not do: player props, futures, live in-play odds updates faster than you schedule it, or books ESPN does not carry.

### Integrations

Run it on a schedule and send the dataset anywhere: Apify API (`/v2/datasets/{id}/items?format=csv`), webhooks, Google Sheets, Zapier, Make, n8n, Airbyte, or call it from LangChain / LlamaIndex / an MCP client as a tool for AI agents ("what are tonight's NBA lines?").

### FAQ

**Is this legal?** The Actor reads the public JSON API behind espn.com's scoreboards, without login or circumvention, and stores only game and price data: team names, public records, scores and sportsbook numbers. No personal data is collected. Betting itself is subject to your local laws; this is data, not a betting service.

**Do I need a proxy?** No. ESPN's API accepts direct connections. If the platform's IP is ever refused, the Actor retries with other browser header profiles and then with Apify Proxy (datacenter) on its own; you can also set `proxyConfiguration`.

**Why does a game have no row?** Lines appear when DraftKings prices the game, usually a few days before game day (college basketball and far-future dates often have none yet), and ESPN drops the embedded scoreboard line once a game is over: for finished games use the default `oddsSource: "core"`, which still returns the closing line. Turn on `includeEventsWithoutOdds` to get the schedule anyway.

**Which sportsbooks?** Whatever ESPN returns: DraftKings for every league, and Bet 365 on some European soccer matches (1X2, draw and goal line). The Actor does not fetch other books on its own.

**How fresh are the lines?** Live from ESPN at run time; schedule the Actor as often as you need. `open…` fields are the opening numbers, `close…` the closing numbers of finished games.

**What are the limits?** Up to 31 days per run; `maxItems` is the only cap on rows. ESPN scoreboards list a league's games per US Eastern day (NFL and college football per week).

### Changelog

- **0.1** (October 2026): first release. 15 leagues plus custom ESPN paths, date ranges, game-state and sportsbook filters, core odds API with scoreboard fallback, opening / current / closing lines in American and decimal odds, scores and spread / total / moneyline results, exact `maxItems`, pay-per-row pricing.

***

### En español

Sports Betting Odds Scraper extrae las cuotas de apuestas por partido desde la API pública de ESPN (las líneas de DraftKings que muestra espn.com): hándicap (spread), total (over/under) y moneyline para NFL, NBA, MLB, NHL, fútbol americano y baloncesto universitario, MLS, Premier League, LaLiga, Bundesliga, Serie A, Ligue 1, Champions League, Liga MX y peleas de UFC. Cada fila es un partido por casa de apuestas, plana y tipada: línea actual, de apertura y de cierre en cuotas americanas y decimales, favorito, hora de inicio, equipos y récords, marcador en vivo o final y, para partidos terminados, quién cubrió el hándicap y si el total fue over o under. Filtros por liga, fechas (hasta 31 días), estado del partido y casa de apuestas. Sin navegador, sin login y sin proxy: ESPN republica las líneas, así que no hace falta raspar DraftKings (que bloquea bots). Consulta la pestaña Pricing para ver los precios vigentes; los planes de pago de Apify tienen tarifas más bajas.

# Actor input Schema

## `leagues` (type: `array`):

Leagues to scrape. NFL and college football default to ESPN's current week when no dates are given; the others use today plus `days` - 1 more days (US Eastern dates).

## `customLeagues` (type: `array`):

Any other league ESPN has a scoreboard for, as `sport/league` exactly like in ESPN URLs: soccer/ned.1 (Eredivisie), soccer/por.1, soccer/uefa.europa, basketball/wnba, football/cfl... Lines exist only where DraftKings prices the league; leagues ESPN does not know are reported and skipped.

## `dateFrom` (type: `string`):

First game day to include, YYYY-MM-DD (US Eastern). Also accepts `today`, `tomorrow` or `+2` (days from today). Empty = today.

## `dateTo` (type: `string`):

Last game day to include (inclusive), YYYY-MM-DD. Empty = `dateFrom` plus `days` - 1. At most 31 days per run.

## `days` (type: `integer`):

How many days to scrape starting at `dateFrom` when `dateTo` is empty. One scoreboard request per league and day.

## `gameStates` (type: `array`):

Keep only games in these states. Empty = all. Finished games come with scores, closing lines and spread / total / moneyline results.

## `providers` (type: `array`):

Keep only these sportsbooks, by name or ESPN provider id (DraftKings = 100, Bet 365 = 2000). Empty = every book ESPN returns for the game.

## `oddsSource` (type: `string`):

`core`: one extra request per game to ESPN's odds API, which gives every sportsbook plus opening, current and closing lines (also for finished games). `scoreboard`: only the DraftKings line embedded in the scoreboard (no extra requests, fastest; upcoming games only).

## `includeEventsWithoutOdds` (type: `boolean`):

Also save a row (with empty odds fields) for games that have no sportsbook line yet, so you still get the schedule. Off by default: rows are only saved, and charged, when there is a line.

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

Stop after this many rows in total (exact; one row = one game x one sportsbook).

## `proxyConfiguration` (type: `object`):

Optional. ESPN's public API accepts direct connections, so no proxy is used by default. If ESPN ever blocks the platform's IP, the Actor retries with other browser headers and then with Apify Proxy (datacenter) by itself.

## Actor input object example

```json
{
  "leagues": [
    "nfl",
    "nba",
    "nhl",
    "mlb",
    "epl"
  ],
  "days": 3,
  "oddsSource": "core",
  "includeEventsWithoutOdds": false,
  "maxItems": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `odds` (type: `string`):

All game x sportsbook rows collected during the run (spread, total, moneyline, open / close, results)

# 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 = {
    "leagues": [
        "nfl",
        "nba",
        "nhl",
        "mlb",
        "epl"
    ],
    "maxItems": 30,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("fabrikit/sports-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 = {
    "leagues": [
        "nfl",
        "nba",
        "nhl",
        "mlb",
        "epl",
    ],
    "maxItems": 30,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("fabrikit/sports-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 '{
  "leagues": [
    "nfl",
    "nba",
    "nhl",
    "mlb",
    "epl"
  ],
  "maxItems": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call fabrikit/sports-odds-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fabrikit/sports-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/DzJdCVQohMBhJc9gs/builds/nEfo6gQjK6Whxtegf/openapi.json
