# OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines (`mrdoe/oddsportal-odds-scraper`) Actor

OddsPortal odds for football, tennis, basketball and more: 1X2, over/under, Asian handicap, BTTS per bookmaker, historic results and live snapshots. Flags pulled (struck-through) prices.

- **URL**: https://apify.com/mrdoe/oddsportal-odds-scraper.md
- **Developed by:** [MrDoe](https://apify.com/mrdoe) (community)
- **Categories:** Other
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $38.00 / 1,000 results

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

![OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines - OddsPortal odds scraper with per-bookmaker odds for football, tennis and basketball](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/oddsportal-odds-scraper--hero.png)

### What does OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines do?

OddsPortal Odds Scraper collects bookmaker odds from OddsPortal and returns one row per match with the odds of every bookmaker for each market you choose. It supports football, tennis, basketball, baseball, American football, ice hockey, handball and volleyball, in three modes: upcoming matches by league or date, historic results of a season, and a live snapshot. Supported markets are 1X2, home/away, both teams to score, double chance, draw no bet, over/under and Asian handicap, and prices that a bookmaker has pulled (struck through on the site) are recorded in blocked\_outcomes.

### Why use OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines?

- Every bookmaker's price per match and market in one row, ready for comparison and modelling.
- Pulled lines are kept and flagged with blocked\_outcomes, because a bookmaker withdrawing a market is a signal.
- Two passes: match links are collected first, so failed matches can be retried without repeating the run.
- Historic seasons with page limits, upcoming by league or date, and live snapshots.
- Kickoff times in UTC and a clean, stable schema.

### What makes this different

Most odds scrapers return one average price per match. This Actor returns each bookmaker's odds for each market, keeps the lines a bookmaker pulled (blocked\_outcomes) rather than dropping them, and lets you retry only failed links.

### Who can use the OddsPortal Odds Scraper and how?

- **Bettors and modellers:** compare every bookmaker's price for a market and find the best odds.
- **Quant and data teams:** build historic odds datasets per bookmaker for model training and backtests.
- **Arbitrage and value-bet builders:** feed odds from many bookmakers into your own detection logic.
- **Line-movement and signal researchers:** use blocked\_outcomes to see when a bookmaker pulled a market.
- **Sports analysts and content creators:** pull closing odds for a season to write about favourites and upsets.
- **Academics:** study bookmaker margins and market efficiency across leagues.

### How it works

![OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines workflow: your input, collection, output](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/oddsportal-odds-scraper--how-it-works.png)

1. **Your input** — choose a sport, a mode and leagues or a date, plus the markets you want.
2. **The Actor collects it** — the Actor collects the match links first and then reads each match page.
3. **Your output** — you get one row per match with every bookmaker's odds for every market.

### What data can you extract?

The dataset has 14 fields per row:

| Field           | Type   | Description                                                                                   |
| --------------- | ------ | --------------------------------------------------------------------------------------------- |
| `match_id`      | string | OddsPortal event id, derived from the match URL.                                              |
| `sport`         | string | Sport.                                                                                        |
| `league`        | string | League and season.                                                                            |
| `home_team`     | string | Home team or player.                                                                          |
| `away_team`     | string | Away team or player.                                                                          |
| `kickoff_utc`   | string | Kickoff time (ISO 8601, UTC).                                                                 |
| `status`        | string | upcoming, live or finished.                                                                   |
| `score`         | string | Score such as 1:2, null when not started.                                                     |
| `markets`       | array  | Market blocks: market, line (totals and handicap), bookmakers with odds and blocked\_outcomes. |
| `match_url`     | string | Link to the match on OddsPortal.                                                              |
| `source`        | string | Data source.                                                                                  |
| `scraped_at`    | string | Collection time (ISO 8601).                                                                   |
| `actor_version` | string | Output schema version.                                                                        |
| `fetch_path`    | string | Which route served the row: always browser for this Actor.                                    |

### How to use OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines

![OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines input form](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/oddsportal-odds-scraper--input.png)

1. Open the Actor and go to the **Input** tab.
2. Choose a **Sport** and **Mode**, enter **Leagues** (for example `england-premier-league`) or a **Date**, pick the **Markets**, and set **Max matches**.
3. Optionally set filters and a **Max results** limit.
4. Click **Start**. A default run finishes in under a minute.
5. Open the **Output** tab, then download the dataset or connect it to your tools.

### Input Parameters

| Parameter            | Type    | Required | Default                   | Description                                                                                                                                                   |
| -------------------- | ------- | -------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sport`              | string  | No       | `"football"`              | Sport to read.                                                                                                                                                |
| `mode`               | string  | No       | `"upcoming"`              | Upcoming matches, finished matches of a season, or a live snapshot.                                                                                           |
| `leagues`            | array   | No       | `[]`                      | League slugs such as england-premier-league or spain-laliga (country-league). Required for historic mode. Leave empty in upcoming mode to use the date below. |
| `date`               | string  | No       | —                         | Day to read as YYYYMMDD. Used in upcoming mode when no league is given.                                                                                       |
| `season`             | string  | No       | `"current"`               | Season as YYYY-YYYY (for example 2024-2025), a year, or current.                                                                                              |
| `markets`            | array   | No       | `["1x2"]`                 | Markets to read: 1x2, home\_away, btts, double\_chance, draw\_no\_bet, over\_under, asian\_handicap. Markets a match does not offer are skipped.                    |
| `matchUrls`          | array   | No       | `[]`                      | OddsPortal match links to read directly, for example the ones in FAILED\_LINKS from an earlier run. Skips the league and date lookup.                          |
| `maxItems`           | integer | No       | `3`                       | Maximum number of matches. Default is 3 for a quick test; set 0 for no limit.                                                                                 |
| `maxPages`           | integer | No       | `3`                       | Maximum number of result pages to read per league and season (50 matches per page).                                                                           |
| `maxLinesPerMarket`  | integer | No       | `3`                       | For over/under and asian handicap: how many lines (for example 2.5 goals) to read per match, starting with the most balanced ones.                            |
| `maxConcurrency`     | integer | No       | `2`                       | Match pages read in parallel. Keep it low to stay polite to the site.                                                                                         |
| `deltaMode`          | boolean | No       | `false`                   | Skip and do not bill matches an earlier run with the same delta key already delivered.                                                                        |
| `deltaStateKey`      | string  | No       | `"default"`               | Name of the tracked stream. Use a different key per league or watchlist.                                                                                      |
| `proxyConfiguration` | object  | No       | `{"useApifyProxy":false}` | Residential proxies are recommended: the site rejects most datacenter addresses.                                                                              |

### Output Data

![OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines dataset table](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/oddsportal-odds-scraper--output.png)

`markets` is a list of market blocks. Each block has a `market`, a `line` for over/under and Asian handicap, and `bookmakers` with `bookmaker_name` and `odds` keyed by outcome (`1`, `X`, `2`, `over`, `under`, `yes`, `no`). `blocked_outcomes` appears only when a bookmaker has pulled an outcome. `kickoff_utc` is in UTC; `score` is null until a match has started.

![OddsPortal Odds Scraper: Bookmaker Odds & Pulled Lines field map of one record](https://api.apify.com/v2/key-value-stores/kE36venAoVchGsE6b/records/oddsportal-odds-scraper--fields.png)

A real dataset item:

```json
{
    "match_id": "xtmHKGT0",
    "sport": "Football",
    "league": "Premier League",
    "home_team": "Arsenal",
    "away_team": "Leeds",
    "kickoff_utc": "2026-10-10T11:30:00.000Z",
    "status": "upcoming",
    "score": null,
    "markets": [
        {
            "market": "1x2",
            "bookmakers": [
                {
                    "bookmaker_name": "888sport",
                    "odds": {
                        "1": 1.36,
                        "2": 7.5,
                        "X": 4.6
                    }
                },
                {
                    "bookmaker_name": "bet-at-home",
                    "odds": {
                        "1": 1.4,
                        "2": 7.25,
                        "X": 4.6
                    }
                },
                {
                    "bookmaker_name": "bet365",
                    "odds": {
                        "1": 1.38,
                        "2": 7.5,
                        "X": 4.5
                    }
                },
                {
                    "bookmaker_name": "Betfury",
                    "odds": {
                        "1": 1.38,
                        "2": 7.6,
                        "X": 4.7
                    }
                },
                {
                    "bookmaker_name": "BetInAsia",
                    "odds": {
                        "1": 1.38,
                        "2": 8.4,
                        "X": 5.2
                    }
                },
                {
                    "bookmaker_name": "Bets.io",
                    "odds": {
                        "1": 1.37,
                        "2": 7.2,
                        "X": 4.7
                    }
                },
                {
                    "bookmaker_name": "Betsson",
                    "odds": {
                        "1": 1.38,
                        "2": 8.7,
                        "X": 4.7
                    }
                },
                {
                    "bookmaker_name": "GGBET",
                    "odds": {
                        "1": 1.44,
                        "2": 7.5,
                        "X": 4.88
                    }
                },
                {
                    "bookmaker_name": "Rainbet",
                    "odds": {
                        "1": 1.39,
                        "2": 7.8,
                        "X": 4.8
                    }
                },
                {
                    "bookmaker_name": "Roobet",
                    "odds": {
                        "1": 1.38,
                        "2": 7.6,
                        "X": 4.7
                    }
                },
                {
                    "bookmaker_name": "Stake.com",
                    "odds": {
                        "1": 1.4,
                        "2": 7.2,
                        "X": 4.8
                    }
                }
            ]
        }
    ],
    "match_url": "https://www.oddsportal.com/football/h2h/arsenal-hA1Zm19f/leeds-tUxUbLR2/#xtmHKGT0",
    "source": "oddsportal",
    "scraped_at": "2026-09-25T21:29:00.960Z",
    "actor_version": "1.0.0",
    "fetch_path": "browser"
}
```

### How to read and use the results

Each row is one match with every bookmaker's odds per market.

1. **Pick the market block** you need in \`markets\`.
2. **Compare bookmakers** by their odds for the same outcome to find the best price.
3. **Work out margin** from the odds of one bookmaker (the sum of 1 divided by each odds).
4. **Watch blocked\_outcomes.** It appears only when a bookmaker pulled an outcome.
5. **Use kickoff\_utc** for time zones; it is always UTC.
6. **Retry only failures** by pasting FAILED\_LINKS into Match links.

### Usage Examples

#### Upcoming Premier League 1X2 odds

```json
{
    "sport": "football",
    "mode": "upcoming",
    "leagues": ["england-premier-league"],
    "markets": ["1x2"],
    "maxItems": 10
}
```

#### A finished season with totals and handicap

```json
{
    "sport": "football",
    "mode": "historic",
    "leagues": ["england-premier-league"],
    "season": "2024-2025",
    "markets": ["1x2", "over_under"],
    "maxItems": 50,
    "maxPages": 2
}
```

#### Live football snapshot

```json
{
    "sport": "football",
    "mode": "live",
    "markets": ["1x2"],
    "maxItems": 20
}
```

#### Only matches not delivered before

```json
{
    "sport": "football",
    "mode": "upcoming",
    "leagues": ["england-premier-league", "spain-laliga"],
    "markets": ["1x2"],
    "maxItems": 0,
    "deltaMode": true,
    "deltaStateKey": "top-leagues"
}
```

### Tips for Best Results

- Start with one market and a small Max matches to check the output, then widen.
- Over/under and Asian handicap read the most balanced lines first; raise Max lines per market for more.
- Space live snapshots at least 60 seconds apart. Repeated runs closer than that risk being flagged.
- Copy links from FAILED\_LINKS into Match links to retry only the failed matches.
- Use residential proxies for larger runs.
- Default is 5 results for a fast test. Set the max to 0 to return everything available.

### Reliability by mode

| Mode                                             | Status | Needs login or key?                | Notes                                                                |
| ------------------------------------------------ | ------ | ---------------------------------- | -------------------------------------------------------------------- |
| Upcoming by league                               | ✅     | No (residential proxy recommended) | League listing plus match pages.                                     |
| Upcoming by date                                 | 🟡     | No                                 | Large dates list many matches; use Max matches.                      |
| Historic seasons                                 | ✅     | No                                 | 50 matches per result page; set Max result pages.                    |
| Live snapshot                                    | 🟡     | No                                 | Single snapshot; live scores can be missing.                         |
| 1X2, home/away, BTTS, double chance, draw no bet | ✅     | No                                 | One block per match.                                                 |
| Over/under and Asian handicap                    | 🟡     | No                                 | Reads the most balanced lines first; raise Max lines per market.     |
| Pulled-price flags (blocked\_outcomes)            | 🟡     | No                                 | Only present when a bookmaker has pulled a price at collection time. |
| Datacenter IPs without a proxy                   | ❌     | -                                  | The site rejects them; use residential proxies on the platform.      |

✅ works as described, 🟡 works with caveats, ❌ not supported.

### Only new records (delta mode)

Turn on **Only new matches (delta mode)** and set a **Delta key**. Matches an earlier run already delivered are skipped before any browser time is spent on them, and are not billed. Use one key per league or watchlist.

### Known Limitations

- For personal research use. OddsPortal's terms of use restrict automated access to the site, so use the Actor at your own risk, keep volumes low and respect their rules. The Actor reads publicly rendered pages only and does not log in.
- On the Apify platform, use residential proxies: the site rejects most datacenter addresses for profile and match pages.
- A wrong league or season combination can return zero results without an error from the site; the Actor logs 0 results for that league and season so you know the combination may be invalid.
- Scores are shown for started matches only, and live scores can be missing.
- Bookmaker lists differ by match and region.
- Pulled-price detection reads the struck-through styling on the site; it only appears when a bookmaker has pulled a price at collection time.
- The site can change its layout; the Actor logs a clear error for pages it cannot read.

### Integrations

Run it from the Apify API, on a schedule, or from a webhook. Send results straight to Google Sheets, Make, Zapier, Slack or your own database with Apify's built-in integrations.

### Export Formats

Download the dataset as JSON, CSV, Excel, XML, HTML table or RSS from the **Output** tab or the API.

### Frequently Asked Questions

#### Where does the data come from?

OddsPortal's publicly rendered match and league pages. It is scraped with a real browser, not an official API. No login is used.

#### Does it need an account or API key?

No. It reads public pages only.

#### How do I get all matches of a season?

Use historic mode with the league and season, and raise Max result pages. Each page holds 50 matches.

#### What does blocked\_outcomes mean?

OddsPortal strikes through a price when a bookmaker pulls a market. The Actor records which outcomes were struck through instead of skipping them. The field is omitted when nothing was pulled.

#### Can I retry only failed matches?

Yes. Failed matches are saved in FAILED\_LINKS; paste them into Match links.

#### Am I charged for failed results?

No. You are only charged for matches that are written to the dataset.

#### How do I get odds from many bookmakers for one match?

Choose the sport and league (or paste the match link), pick the market such as 1x2 or over\_under, and run. Each row lists every bookmaker with its odds.

#### Do I need an account or login?

No. The Actor reads public pages only and never logs in.

#### Am I charged for failed runs or empty results?

You are only charged for results that are actually written to the dataset.

#### Can I run it on a schedule?

Yes. Create a Task with your input and add a schedule in Apify Console. With monitor mode on, each scheduled run returns only what changed since the previous one.

### Changelog

- **2026-09-26:** Added delta mode, deep-match pricing tier and the fetch\_path field. Fixed unknown-season detection and league checks that returned the wrong league.
- **2026-09-25:** First release: seven markets, upcoming, historic and live modes, blocked\_outcomes flag.

### Enterprise and custom work

Need higher volumes, a custom output schema, dedicated scheduling or a no-breaking-changes commitment for a production pipeline? Open an issue on the Actor page and describe your use case. Bulk terms and custom builds are available.

### Support

Questions or a missing field? Open an issue from the **Issues** tab on this Actor's page and it will be looked at.

### Legal / Responsible Use

For personal research use. OddsPortal's terms of use restrict automated access to the site, and you are responsible for complying with them and with applicable law. The Actor reads publicly rendered pages only, never bypasses a login or paywall, and is not affiliated with OddsPortal. This is not betting advice.

# Actor input Schema

## `sport` (type: `string`):

Sport to read.

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

Upcoming matches, finished matches of a season, or a live snapshot.

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

League slugs such as england-premier-league or spain-laliga (country-league). Required for historic mode. Leave empty in upcoming mode to use the date below.

## `date` (type: `string`):

Day to read as YYYYMMDD. Used in upcoming mode when no league is given.

## `season` (type: `string`):

Season as YYYY-YYYY (for example 2024-2025), a year, or current.

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

Markets to read: 1x2, home\_away, btts, double\_chance, draw\_no\_bet, over\_under, asian\_handicap. Markets a match does not offer are skipped.

## `matchUrls` (type: `array`):

OddsPortal match links to read directly, for example the ones in FAILED\_LINKS from an earlier run. Skips the league and date lookup.

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

Maximum number of matches. Default is 3 for a quick test; set 0 for no limit.

## `maxPages` (type: `integer`):

Maximum number of result pages to read per league and season (50 matches per page).

## `maxLinesPerMarket` (type: `integer`):

For over/under and asian handicap: how many lines (for example 2.5 goals) to read per match, starting with the most balanced ones.

## `maxConcurrency` (type: `integer`):

Match pages read in parallel. Keep it low to stay polite to the site.

## `deltaMode` (type: `boolean`):

Skip and do not bill matches an earlier run with the same delta key already delivered.

## `deltaStateKey` (type: `string`):

Name of the tracked stream. Use a different key per league or watchlist.

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

Residential proxies are recommended: the site rejects most datacenter addresses.

## Actor input object example

```json
{
  "sport": "football",
  "mode": "upcoming",
  "leagues": [
    "england-premier-league"
  ],
  "season": "current",
  "markets": [
    "1x2"
  ],
  "matchUrls": [],
  "maxItems": 3,
  "maxPages": 3,
  "maxLinesPerMarket": 3,
  "maxConcurrency": 2,
  "deltaMode": false,
  "deltaStateKey": "default",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `results` (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 = {
    "sport": "football",
    "mode": "upcoming",
    "leagues": [
        "england-premier-league"
    ],
    "markets": [
        "1x2"
    ],
    "maxItems": 3,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrdoe/oddsportal-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 = {
    "sport": "football",
    "mode": "upcoming",
    "leagues": ["england-premier-league"],
    "markets": ["1x2"],
    "maxItems": 3,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("mrdoe/oddsportal-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 '{
  "sport": "football",
  "mode": "upcoming",
  "leagues": [
    "england-premier-league"
  ],
  "markets": [
    "1x2"
  ],
  "maxItems": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call mrdoe/oddsportal-odds-scraper --silent --output-dataset

```

## MCP server setup

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