# Sports Betting Odds Scraper: NFL, NBA, MLB & Soccer Lines (`sourabhbgp/sports-odds-scraper`) Actor

Scrape prematch betting odds for NFL, NBA, MLB, NHL, college and top soccer. Every sportsbook's moneyline, spread and total in one row per game: 5 US books, up to 20 in the UK, 26 in Brazil. Opening price and movement on each, plus vig free fair odds and arbitrage.

- **URL**: https://apify.com/sourabhbgp/sports-odds-scraper.md
- **Developed by:** [Sourabh Kumar](https://apify.com/sourabhbgp) (community)
- **Categories:** Sports, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 game with odds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### Sports betting odds scraper: moneyline, spread, totals and line movement

Pull prematch betting lines for NFL, NBA, MLB, NHL, college and top soccer. One row per game, with every sportsbook's price nested side by side.

Every price carries its **opening number**, so you get line movement without storing yesterday's run.

**$1.00 per 1,000 games on any paid plan, $2.00 on the Free plan. No per run fee.**

Read the US market for DraftKings, FanDuel, BetMGM, Fanatics and bet365. Switch to the UK for up to 20 books, or Brazil for 26.

### Why this scraper?

- 🏦 **Up to 26 sportsbooks per game**, not the two or three you get from scrapers that hit each book directly.
- 📉 **Line movement is built in.** Every price ships with the opening number and an up/down/unchanged flag.
- 🧮 **Vig free fair odds** on every complete market, plus the average overround so you can see what the book is holding.
- 💸 **Arbitrage detection with stake splits**, calculated from the best price on each outcome across all books.
- 🛒 **Best price per outcome** and how far it beats the market average, which is line shopping done for you.
- 🚫 **No proxy, no API key, no browser.** That is why it is cheap and why it does not break when a sportsbook tightens its bot rules.
- 🧾 **One row is one whole game.** Scrapers that bill per bookmaker line charge you 20 to 60 times for the same fixture.
- 🎯 **No per run fee, and skipped games are never charged.** Fixtures with no published prices are crawled silently and cost you nothing.
- 🌍 **Seven odds markets** across the US, UK, Brazil and Germany, so you are not stuck with US books.

### What data can you extract?

<table>
  <tr><td>🏟️ Teams and logos</td><td>🕒 Start time (UTC)</td><td>🏆 League</td><td>📊 Final score</td></tr>
  <tr><td>💰 Moneyline</td><td>📏 Spread / handicap</td><td>🔢 Total (over / under)</td><td>🏦 Bookmaker name</td></tr>
  <tr><td>📈 Opening price</td><td>📉 Movement direction</td><td>🇺🇸 American odds</td><td>🔟 Decimal odds</td></tr>
  <tr><td>🎲 Implied probability</td><td>🧮 Vig free fair odds</td><td>💸 Arbitrage + stakes</td><td>🛒 Best price per outcome</td></tr>
</table>

### Leagues and odds markets covered

**Leagues.** NFL, NCAA Football, CFL, NBA, WNBA, NCAA Basketball, MLB, NHL, MLS, Premier League, LaLiga, Bundesliga, Serie A, Ligue 1, Champions League, Europa League, Conference League, ATP and WTA. Or sweep a whole sport instead of picking leagues.

**Odds markets.** Each game can be priced from a different country's bookmakers. The market you choose changes which books appear, not which games.

| Market | Books | Examples |
|---|--:|---|
| United States (New Jersey) | 5 | DraftKings, FanDuel, BetMGM, Fanatics, bet365 |
| United Kingdom | up to 20 | bet365, William Hill, Betfair, Paddy Power, Ladbrokes, Skybet |
| Brazil | up to 26 | Betano, bet365, Betfair, KTO, Superbet, Betnacional |
| Germany | 7 | Betano, Interwetten, Winamax, Oddset, bet365 |

Colorado, Illinois and New York are also selectable for US runs. New Jersey is the default because it carries the widest book list.

### How to scrape betting odds: step by step

1. [Create a free Apify account](https://console.apify.com/sign-up). Takes 30 seconds, no card needed.
2. Open [Sports Betting Odds Scraper](https://console.apify.com/actors/a8JEbJvOov844ebDv?addFromActorId=a8JEbJvOov844ebDv) in the Apify Console.
3. Pick your leagues and an odds market. That is the whole setup.
4. Click **Start**. A typical slate finishes in under a minute.
5. Export as JSON, CSV, or Excel, or fetch it from the API.

### How much does Sports Betting Odds Scraper cost?

You pay per game delivered. Every paid Apify plan pays **$1.00 per 1,000 games**, and the Free plan pays **$2.00 per 1,000**. There is **no per run fee**, so a run costs exactly what it delivers.

One row is one whole game with every bookmaker nested inside it. Scrapers that bill per bookmaker line charge you 20 to 60 times for that same game, so compare on games, not rows.

The Free plan's $5 of monthly credit covers roughly **2,500 games**. The $29/month Starter plan covers about **29,000 games** a month.

Games that have no published odds are skipped and never billed, so a run over an empty slate costs you nothing at all.

### Input

```json
{
  "leagues": ["nfl", "nba", "mlb", "nhl"],
  "oddsCountry": "US",
  "markets": ["moneyline", "spread", "total"],
  "includeAlternateLines": false,
  "computeFairLines": true,
  "computeArbitrage": true,
  "maxItems": 100
}
```

| Field | Type | Default | Note |
|---|---|---|---|
| `leagues` | array | none | Any of the 19 leagues listed above. |
| `sports` | array | none | Sweep every competition in a sport instead of picking leagues. |
| `oddsCountry` | string | `US` | Which country's bookmakers price the games. |
| `markets` | array | moneyline, spread, total | Add `btts`, `double_chance`, `draw_no_bet`, `correct_score`, `odd_even`. |
| `statuses` | array | all | Limit to `scheduled`, `live` or `finished`. |
| `dateFrom` / `dateTo` | string | today, tomorrow | `YYYY-MM-DD`. Leave both out for today and tomorrow. You can reach 7 days back through tomorrow, and nothing beyond that. |
| `includeAlternateLines` | boolean | `false` | Every published spread and total, not just the primary line. |
| `includePeriodMarkets` | boolean | `false` | Add first half, second half and first quarter markets. |
| `computeFairLines` | boolean | `true` | Vig free consensus probabilities per market. |
| `computeArbitrage` | boolean | `true` | Flag guaranteed profit opportunities with stake splits. |
| `onlyArbitrage` | boolean | `false` | Return only games that contain one. Others are skipped unbilled. |
| `minArbitrageProfitPct` | integer | `0` | Ignore arbitrage below this guaranteed return. `0` reports any positive edge. |
| `matchIds` | array | none | Price specific games directly, ignoring league and date filters. |
| `maxItems` | integer | `100` | One game is one row and one charge. `0` for no limit. |

Only arbitrage, priced from the widest market:

```json
{ "sports": ["soccer"], "oddsCountry": "BR", "onlyArbitrage": true, "maxItems": 50 }
```

### Output

One row per game. Prices, best prices, fair lines and arbitrage are nested inside it.

```json
{
  "matchId": "OfrMA53K",
  "url": "https://www.flashscore.com/match/OfrMA53K/",
  "sport": "americanFootball",
  "league": "nfl",
  "leagueLabel": "NFL",
  "country": "USA",
  "startTime": "2026-08-13T23:30:00.000Z",
  "status": "finished",
  "home": { "name": "New England Patriots", "teamId": "f9amdMmC", "slug": "new-england-patriots" },
  "away": { "name": "Indianapolis Colts", "teamId": "SMcef0IO", "slug": "indianapolis-colts" },
  "homeScore": 13,
  "awayScore": 13,
  "winner": null,
  "oddsCountry": "US",
  "bookmakerCount": 5,
  "priceCount": 26,
  "marketCount": 7,
  "movedCount": 24,
  "hasArbitrage": false
}
```

Each entry in `prices` is one book's number for one side of one market:

```json
{
  "bookmakerName": "bet365.us",
  "market": "moneyline",
  "side": "home",
  "teamName": "New England Patriots",
  "selection": null,
  "line": null,
  "decimal": 2.5,
  "american": 150,
  "openingDecimal": 1.74,
  "movement": "up",
  "impliedProbability": 0.4,
  "isActive": true
}
```

#### How spread and total lines are written

On a spread, **each side carries its own number**: the home team at `+1.5` and the away team at `-1.5` are the two halves of one market. Totals work the same way, with `selection` set to `OVER` or `UNDER` on a shared line.

So every entry that belongs to a market also carries `marketLine`, which is identical on both sides. **Group by `marketLine` to put a market back together**; read `line` to see the number you would actually be betting.

```json
{ "market": "spread", "side": "home", "teamName": "New England Patriots", "line": "1.5",  "marketLine": "1.5", "decimal": 2.1,  "american": 110,  "bookmakerName": "Fanatics", "edgeVsAveragePct": 0,    "bookCount": 2 }
{ "market": "spread", "side": "away", "teamName": "Indianapolis Colts",   "line": "-1.5", "marketLine": "1.5", "decimal": 1.74, "american": -135, "bookmakerName": "Fanatics", "edgeVsAveragePct": 0.87, "bookCount": 2 }
```

That is `bestPrices`: the top number available on each side, which book is offering it, and how far it beats the average of every book pricing that same side.

Each entry in `fairLines` is the same market with the bookmaker margin stripped out. `overround` is what the average book is holding, so `1.043` means 4.3%:

```json
{
  "market": "moneyline",
  "line": null,
  "overround": 1.04332,
  "outcomes": [
    { "side": "home", "teamName": "New England Patriots", "line": null, "fairProbability": 0.391377, "fairDecimal": 2.5551 },
    { "side": "away", "teamName": "Indianapolis Colts",   "line": null, "fairProbability": 0.608623, "fairDecimal": 1.6431 }
  ]
}
```

`arbitrage` gives the legs of any guaranteed profit spot, each with the line to take, the book offering it, and the share of your stake it should get.

### Use cases

- 📉 **Line movement tracking.** Every price ships with its opening number, so steam moves show up on the first run.
- 🛒 **Line shopping.** Compare up to 26 books on one game and take the best number on each side.
- 💸 **Arbitrage scanning.** Surface guaranteed profit spots across books, with the stake split already worked out.
- 🧮 **Model building and backtesting.** Vig free fair probabilities are a cleaner training target than raw prices.
- 🎯 **Closing line value.** Prices freeze at kickoff, so the last prematch number is the closing line.
- 📰 **Odds widgets and previews.** Feed a "tonight's lines" table on a sports content site.
- 🤖 **Alert bots.** Schedule a run every few minutes and push moves into Slack or Discord.
- 📊 **Market efficiency research.** Compare overround across books, leagues and countries.

### Limitations

- **Prematch only.** Prices freeze once a game goes in play, so this is not a live in-play odds feed.
- **No player props or futures.** The source carries game lines only: moneyline, spread, total and related markets.
- **9 day window.** You can reach 7 days back through tomorrow. There is no historical archive.
- **Older finished games lose their odds.** Prices stay available for a while after a game ends, then the source removes them. Scrape before kickoff if you want a guaranteed closing line.
- **Fair odds are a consensus, not a sharp line.** They average the books present rather than anchoring to a single sharp book.
- **Out of season leagues return nothing.** Ask for the NBA in July and the run tells you so instead of failing.

### FAQ

#### How much does Sports Betting Odds Scraper cost?

It uses pay per result pricing. You pay **$1.00 for 1,000 games on any paid plan**, or $2.00 per 1,000 on the Free plan, with no per run fee. The Apify Free plan gives you $5 in usage credits a month, enough for around 2,500 games. If you run regularly, the $29/month Starter plan covers about 29,000 games.

No subscription lock in. Pause whenever.

#### Is it legal to scrape betting odds?

Scraping public data is generally allowed in the US and most of the EU, as long as you don't collect personal data covered by GDPR or CCPA without a lawful basis. This actor only touches publicly accessible pages, and odds are commercial pricing data rather than personal data. How you use the output is on you.

Apify's full breakdown: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

#### Can I integrate it with other tools?

Push results into **Make, Zapier, Slack, Airbyte, GitHub, Google Sheets, Google Drive**, and more. Apify treats every actor as a webhook source, so anything that consumes webhooks or pulls from an API works.

Full list: [Apify integrations](https://docs.apify.com/platform/integrations).

#### Can I use it with the Apify API?

Yes. Every run is available via the Apify REST API:

```bash
curl -X POST "https://api.apify.com/v2/acts/sourabhbgp~sports-odds-scraper/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"leagues": ["nfl", "nba", "mlb", "nhl"], "oddsCountry": "US", "maxItems": 50}'
```

Docs: [Apify API reference](https://docs.apify.com/api/v2).

#### Can I use it through an MCP Server?

Yes. Apify ships an MCP server that exposes every actor as a tool, so Claude Desktop, Cursor, and any other MCP capable client can call it. Setup: [Apify MCP docs](https://docs.apify.com/platform/integrations/mcp).

### Your feedback

Bug, missing field, or odd behavior? Drop a note in the [Issues tab](https://console.apify.com/actors/a8JEbJvOov844ebDv/issues). Reports go to a human and fixes usually ship the same week.

# Actor input Schema

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

Leagues to pull odds for. Pick any combination. A league that is out of season simply returns nothing and the run says so.

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

Optional. Sweep every competition in a sport, not just the leagues above. Much broader and slower than a league selection.

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

First day to include, as YYYY-MM-DD. Flashscore publishes 7 days back through tomorrow, so older dates are trimmed to that limit.

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

Last day to include, as YYYY-MM-DD. Defaults to tomorrow, which is the furthest ahead Flashscore publishes.

## `oddsCountry` (type: `string`):

Which country's bookmakers to price the games with. US returns DraftKings, FanDuel, BetMGM, Fanatics and bet365. GB returns 20 UK books and BR returns 26.

## `markets` (type: `array`):

Which betting markets to return. Moneyline is who wins, spread is the point spread or handicap, total is the over/under. Leave empty for these three.

## `statuses` (type: `array`):

Limit to scheduled, live or finished games. Prices freeze at kickoff, so live and finished games return the last prematch price and not an in play price. Older finished games have their odds removed by the source entirely.

## `includeAlternateLines` (type: `boolean`):

Return every published spread and total, not only the primary line. Adds a lot of rows per game: an NFL game can carry 270 alternate totals.

## `includePeriodMarkets` (type: `boolean`):

Also return first half, second half and first quarter markets alongside the full game line.

## `computeFairLines` (type: `boolean`):

Add consensus probabilities with the bookmaker margin removed, plus the average overround per market.

## `computeArbitrage` (type: `boolean`):

Flag games where taking the best price on every outcome across books guarantees a profit, with the stake split for each leg.

## `minArbitrageProfitPct` (type: `integer`):

Ignore arbitrage opportunities below this guaranteed return. 0 reports any positive edge.

## `onlyArbitrage` (type: `boolean`):

Return only games that contain at least one arbitrage opportunity. Games without one are skipped and never charged.

## `matchIds` (type: `array`):

Optional. Flashscore game IDs to price directly, ignoring the league and date filters. The whole published window is searched.

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

Stop after this many games. One game is one row and one charge. Set 0 for no limit.

## Actor input object example

```json
{
  "leagues": [
    "nfl",
    "nba",
    "mlb",
    "nhl"
  ],
  "sports": [],
  "oddsCountry": "US",
  "markets": [
    "moneyline",
    "spread",
    "total"
  ],
  "statuses": [],
  "includeAlternateLines": false,
  "includePeriodMarkets": false,
  "computeFairLines": true,
  "computeArbitrage": true,
  "minArbitrageProfitPct": 0,
  "onlyArbitrage": false,
  "matchIds": [],
  "maxItems": 50
}
```

# Actor output Schema

## `results` (type: `string`):

View the scraped odds rows in the dataset.

# 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",
        "mlb",
        "nhl"
    ],
    "oddsCountry": "US",
    "markets": [
        "moneyline",
        "spread",
        "total"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("sourabhbgp/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",
        "mlb",
        "nhl",
    ],
    "oddsCountry": "US",
    "markets": [
        "moneyline",
        "spread",
        "total",
    ],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("sourabhbgp/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",
    "mlb",
    "nhl"
  ],
  "oddsCountry": "US",
  "markets": [
    "moneyline",
    "spread",
    "total"
  ],
  "maxItems": 50
}' |
apify call sourabhbgp/sports-odds-scraper --silent --output-dataset

```

## MCP server setup

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