# Football Data API: Results, Closing Odds & Fixtures (`smoked_drift/european-football-results-odds`) Actor

Football and soccer data API for 22 European leagues: results, match stats, closing odds from 9+ bookmakers, implied probabilities, line movement, Elo and form. Premier League, Ligue 1, LaLiga and Serie A, plus upcoming fixtures.

- **URL**: https://apify.com/smoked\_drift/european-football-results-odds.md
- **Developed by:** [Titouan MARTY](https://apify.com/smoked_drift) (community)
- **Categories:** Sports
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 match extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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

## Football Data API: Results, Closing Odds & Fixtures

**Football data API** for 22 European leagues — football results, match statistics, **closing odds** from 9+ bookmakers, implied probabilities, bookmaker margin, line movement, Elo ratings and recent form, plus upcoming **fixtures**.

Built for data analysts, modellers, betting researchers, fantasy players and content teams who need a **clean, stable, reproducible** football dataset instead of a fragile screen-scraper. Works as a **soccer data** source for Python, JavaScript, R or any other language, and as a tool for AI agents over MCP.

***

### Why this one is different: it cannot break the way scrapers do

Most football Actors scrape HTML. Football sites redesign, add anti-bot layers, and change their markup — and the Actor starts failing. Across the Apify Store, sports and news scrapers fail between **6% and 33% of the time**, depending on the vertical.

This Actor does not parse a single web page. It downloads the machine-readable CSV files that **football-data.co.uk publishes for direct download** — a source that has been maintained continuously for over two decades. There are **no CSS selectors to break, no browser to keep alive, no proxy to buy, and no anti-bot arms race**. If the archive is up, this Actor works.

Every record carries its exact source URL, so any row can be traced back to the file it came from.

***

### What you get

One dataset item per match:

| Group | Fields |
| --- | --- |
| **Identity** | `matchId` (stable across runs), `league`, `season`, `date`, `kickoff`, `home`, `away`, `status` |
| **Result** | `score` (home / away / H-D-A), `halfTime` |
| **Stats** | shots, shots on target, corners, fouls, yellow and red cards — home and away |
| **Odds** | opening **and** closing prices per bookmaker (B365, Pinnacle, Betfair, William Hill, Betway, Interwetten, Max, Avg and more) |
| **Market** | best available price per outcome, consensus implied probability, average bookmaker margin, margin-free fair price, **line movement** from opening to closing |
| **Analytics** | pre-match **Elo** for both teams, home-win expectation, last-5 form and points |
| **Provenance** | provider name and exact source URL |

**Nothing leaks the future.** Elo ratings and form are always the value *before* kick-off, so a row never encodes the result it describes. That is what makes this dataset safe to backtest on.

#### A real row, unedited

```json
{
  "league": "F1",
  "date": "2026-09-13",
  "home": "Lille",
  "away": "Troyes",
  "status": "finished",
  "score": { "home": 2, "away": 0, "result": "H" },
  "market": {
    "bestPrice": { "home": 1.53, "draw": 4.80, "away": 7.00 },
    "bookmakerCount": 9,
    "closingAvailable": true,
    "impliedProbability": { "home": 0.6421, "draw": 0.2061, "away": 0.1518 },
    "averageMargin": 0.0528,
    "fairPrice": { "home": 1.557, "draw": 4.853, "away": 6.586 },
    "lineMovement": { "B365.away": -1.5, "Avg.away": -1.34, "Max.away": -1.4 }
  },
  "elo": { "home": 1484.4, "away": 1519.4, "homeWinExpectation": 0.5429 },
  "form": {
    "home": { "last5": "DDLL", "pointsLast5": 2, "matchesPlayed": 4 },
    "away": { "last5": "WWLW", "pointsLast5": 9, "matchesPlayed": 4 }
  }
}
```

Nine bookmakers, a 5.3% average margin, and a clear away-side steam move from open to close — the kind of detail that normally takes a weekend of scraping to assemble.

***

### Two sources, one shape

| Source | Contains |
| --- | --- |
| Per-season archives | Every **played** match of the season, with full closing odds |
| Live fixtures feed | The **upcoming** matches (rolling window), with opening and closing odds |

The per-season archives only contain matches that have already been played — so the fixtures feed is what gives you forward-looking rows. Both are normalised into the same record shape and returned together. Turn fixtures off with `includeFixtures: false`.

***

### Monitor mode: schedule it and get told what moved

With `mode: "changes"`, the Actor keeps a snapshot in a key-value store record and returns **only matches that are new, or whose result changed** since the previous run.

```
Run 1  ->  90 matches emitted   (everything is new)
Run 2  ->   0 matches emitted   (nothing moved — nothing billed)
Run 3  ->   1 match emitted     (a fixture just finished)
```

That is the difference between a one-off export and a monitoring product. Pair it with a schedule to get a daily results-and-odds digest, and use `stateKey` to keep separate monitors (one per league set, for example).

***

### Usage

#### Historical dataset for backtesting

```json
{
  "leagues": ["big-five-plus-second"],
  "seasonsBack": 5,
  "onlyFinished": true,
  "withAnalytics": true,
  "maxItems": 20000
}
```

#### Only French football

```json
{ "leagues": ["france"], "seasonsBack": 2 }
```

#### Daily monitoring of one team

```json
{
  "leagues": ["big-five"],
  "mode": "changes",
  "teams": ["Marseille"],
  "includeFixtures": true
}
```

Schedule it daily. Each run returns only what changed.

#### Upcoming fixtures with current prices

```json
{ "leagues": ["all"], "includeFixtures": true, "onlyFinished": false }
```

***

### Leagues

`E0` `E1` `E2` `E3` `EC` (England) · `SC0` `SC1` `SC2` `SC3` (Scotland) · `D1` `D2` (Germany) · `I1` `I2` (Italy) · `SP1` `SP2` (Spain) · `F1` `F2` (France) · `N1` (Netherlands) · `B1` (Belgium) · `P1` (Portugal) · `T1` (Turkey) · `G1` (Greece)

**Presets:** `big-five` · `big-five-plus-second` · `france` · `england` · `all`

***

### Use it from an AI agent

Every input field is typed and documented, so this Actor works as a tool out of the box. The common agent calls are:

| You want | Set |
| --- | --- |
| One team's recent matches | `teams: ["Marseille"]`, `seasonsBack: 1` |
| Today's fixtures and prices | `includeFixtures: true`, `onlyFinished: false`, `seasonsBack: 1` |
| A backtest set | `leagues: ["big-five"]`, `seasonsBack: 5`, `onlyFinished: true` |
| Only what changed since yesterday | `mode: "changes"` + a daily schedule |

Apify's MCP server exposes this Actor to Claude, Cursor, and other MCP clients automatically — no wrapper needed.

***

### Reliability

This Actor was tested against live data before release, not just compiled:

- 22 league-season files parsed, plus the live fixtures feed
- opening and closing prices correctly separated across 113 source columns
- Elo verified to start at 1500/1500 on matchday 1 (no result leakage)
- monitoring mode verified idempotent: a second run with no new results emits **zero** rows and bills **zero** events

The source is a plain CSV over HTTPS, so there is no browser, no proxy, and no anti-bot layer between you and the data.

***

### Pricing

Two pay-per-event events, so you control exactly what you pay for:

| Event | Charged | Covers |
| --- | --- | --- |
| `match-extracted` | once per match returned | the full record: result, stats, odds, provenance |
| `analytics-attached` | once per match, when `withAnalytics` is on | Elo, form, market read, line movement |

Set `withAnalytics: false` to pay for raw data only. `maxItems` caps the total, and in `changes` mode you are only ever billed for matches that actually changed — a quiet day costs nothing.

***

### Use cases

- **Backtesting** a model on clean historical results with genuine closing odds
- **Closing-line value (CLV)** analysis: compare the price you took against the closing consensus
- **Line-movement research**: the `market.lineMovement` field records open-to-close drift per bookmaker
- **Content and editorial**: auto-generate tables, form guides and match previews
- **Fantasy and tipping analysis** based on Elo, form and margin-free fair prices
- **AI agents**: the input schema is typed and every field is documented, so this runs cleanly as a tool over MCP

***

### Notes and limits

- **Odds coverage varies by division.** Major leagues carry 9+ bookmakers; lower divisions carry fewer, and non-prime markets may be missing. `market.bookmakerCount` tells you exactly how many fed each row.
- **The fixtures feed is a rolling window**, typically a few days ahead, and only covers divisions with matches in that window. Run it on a schedule so you never miss a fixture appearing.
- **Team names are the provider's**, in English where a common English name exists (for example `Bayern Munich`, not `Bayern München`). Filter with `teams` using partial, case-insensitive matching.
- **Analytics are computed per run** over the rows in scope. A wider `seasonsBack` gives Elo ratings time to converge; a single season starts every team at 1500.
- This Actor provides **data and analysis only**. It is not betting advice, and it does not predict outcomes.

***

### Data source

Football results, match statistics and bookmaker prices: [football-data.co.uk](https://www.football-data.co.uk/data.php), distributed as freely downloadable CSV files. This Actor is sometimes described as a **football-data.co.uk scraper** — except that it downloads the published files directly, so there are no selectors to break.

### Related Actors

From the same publisher, on the same engine and the same 22 leagues:

- [Football Odds Comparison](https://apify.com/smoked_drift/football-odds-comparison) — best available price for every outcome across 9+ bookmakers, plus the opening-to-closing line movement
- [Football Stats & Elo Ratings](https://apify.com/smoked_drift/football-team-stats-elo) — one row per club: Elo, record, home and away splits, form and scoring profile

Building a wider pipeline? These Apify Actors pair well with a football data API:

- [Google Maps Scraper](https://apify.com/compass/crawler-google-places) — anything location-based around clubs, stadiums and venues
- [Instagram Scraper](https://apify.com/apify/instagram-scraper) — club and player social data, for reach or sentiment analysis
- [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) — track how coverage moves around a fixture

If you need a competition or market this Actor does not cover, open an Issue on this page — coverage is driven by demand.

### Support

Found a broken row or a missing league? Report it in the **Issues** tab of this Actor. Public Actors are maintained actively; fixes ship quickly.

# Actor input Schema

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

Which competitions to include. Use a preset ('big-five', 'france', 'england', 'big-five-plus-second', 'all') or explicit codes. Codes: E0-E3, EC (England), SC0-SC3 (Scotland), D1-D2 (Germany), I1-I2 (Italy), SP1-SP2 (Spain), F1-F2 (France), N1 (Netherlands), B1 (Belgium), P1 (Portugal), T1 (Turkey), G1 (Greece).

## `seasonsBack` (type: `integer`):

How many seasons of history to download, starting from the current one. 1 = current season only. Each extra season adds roughly 380 matches per league.

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

dataset = return every matching match. changes = monitoring mode: return only matches that are new or whose result changed since the previous run, using a stored snapshot. Use changes with a schedule to build a results alert.

## `includeFixtures` (type: `boolean`):

Also pull the live fixtures feed: upcoming matches with opening and closing odds. This is the only source of not-yet-played matches, since the per-season archives contain results only.

## `teams` (type: `array`):

Optional. Keep only matches involving these teams. Case-insensitive partial match, so 'Marseille' also matches 'Olympique Marseille'.

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

Optional lower bound on the match date.

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

Optional upper bound on the match date.

## `onlyFinished` (type: `boolean`):

Drop upcoming fixtures. Useful when you are building a historical dataset for backtesting.

## `onlyWithOdds` (type: `boolean`):

Drop matches with no usable price data. On by default, because a match without odds has no market read attached.

## `withAnalytics` (type: `boolean`):

Compute pre-match Elo ratings, recent form, consensus implied probabilities, bookmaker margin and line movement. Adds a second billable event per match.

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

Hard cap on how many matches are returned. Protects your budget on wide scopes: 22 leagues x 3 seasons is about 25,000 matches.

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

Name of the key-value store record holding the snapshot used by 'changes' mode. Keep the same value across runs; change it to start monitoring from scratch.

## Actor input object example

```json
{
  "leagues": [
    "france"
  ],
  "seasonsBack": 1,
  "mode": "dataset",
  "includeFixtures": true,
  "onlyFinished": false,
  "onlyWithOdds": true,
  "withAnalytics": true,
  "maxItems": 5000,
  "stateKey": "monitor-state"
}
```

# Actor output Schema

## `matches` (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 = {
    "leagues": [
        "france"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("smoked_drift/european-football-results-odds").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": ["france"] }

# Run the Actor and wait for it to finish
run = client.actor("smoked_drift/european-football-results-odds").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": [
    "france"
  ]
}' |
apify call smoked_drift/european-football-results-odds --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,smoked_drift/european-football-results-odds"
        }
    }
}
```

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/J4bfpfC3gTNXg3ulX/builds/mWE53zrpyOBqBesEm/openapi.json
