# Flashscore Matches Scraper: Scores, Timeline and Odds (`mrbridge/flashscore-matches-scraper`) Actor

Scrape Flashscore matches for football, tennis and basketball into one row per match: teams, score, half-time and period scores, status, start time and winner, for any day up to 7 days back or ahead. Add goals, cards and substitutions, and UK bookmaker odds. JSON, CSV or Excel. By MrBridge

- **URL**: https://apify.com/mrbridge/flashscore-matches-scraper.md
- **Developed by:** [MrBridge](https://apify.com/mrbridge) (community)
- **Categories:** News, Other
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.35 / 1,000 matches

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

## Flashscore Matches Scraper

Flashscore Matches Scraper collects football, tennis and basketball matches from Flashscore (https://www.flashscore.com), one clean row per match, ready for JSON, CSV or Excel. It reads the public match feeds of the site, so no account and no API key are needed. Each row gives the teams or players, the tournament, the start time, the status, the score, the half-time score, the winner and a link to the match page. You can add the timeline of incidents of football matches and the betting odds of each match.

Pick `football`, keep today as the day and set `maxItems` to 20: the Actor returns 20 matches with their tournament, kick-off time, status and score. Turn on the timeline to get each goal, card and substitution with its minute and player, or turn on the odds to get the prices that operators listed in the United Kingdom show for the match.

### What is Flashscore Matches Scraper?

Flashscore is a live-score website that lists the matches of thousands of tournaments, with scores updated during play. The Actor reads its day feeds for you. It selects sports and days, applies your league filter, removes duplicates and returns one structured row per match. The first 23 columns follow a fixed sports-matches layout, documented below, so the rows are easy to compare with other sources that use the same layout.

#### What data can I extract with it?

One row per match: source, match ID, match link, sport, country, tournament and its ID, season, round, start time, normalized status and status as Flashscore writes it, elapsed minute of a live match, home and away team with their IDs, score, half-time score, winner and scrape time. Then the Flashscore columns: tournament stage ID, tournament category, tournament link, country ID, score of each period, note, last update time, and the optional `timeline` and `odds`.

#### How can I use the results?

- Build a results archive of the leagues you follow, one day at a time.
- Feed a dashboard or a newsletter with today's fixtures and yesterday's scores.
- Study how often home teams win in a tournament, using the winner and half-time columns.
- Track the goals, cards and substitutions of finished football matches.
- Compare the odds shown by several operators for the same match, as information for your own analysis.

### How to use it

1. Open the Actor in Apify Console.
2. Pick the sports and the days, and add leagues if you want to narrow the list.
3. Set `maxItems`, then start the run. Rows appear in the dataset as they are delivered.

### Input

| Field | Meaning | Default |
|---|---|---|
| `sports` | `football`, `tennis`, `basketball`. At least one | `["football"]` |
| `dateOffsets` | Days from today (UTC): 0 is today, -1 yesterday, 1 tomorrow. Integers from -7 to 7 | `[0]` |
| `dates` | Calendar days as `YYYY-MM-DD`, within 7 days of today (UTC). Added to `dateOffsets` | empty |
| `leagues` | Keep only these tournaments, up to 100. Empty keeps all (see the four forms below) | empty |
| `maxItems` | Hard cap on match rows returned and billed, 1 to 10000 | 100 |
| `includeTimeline` | Add the timeline of incidents of football matches | `false` |
| `includeOdds` | Add the betting odds of each match | `false` |
| `oddsMarkets` | Markets kept in `odds`. At least one. `ALL` keeps every market | `HOME_DRAW_AWAY`, `HOME_AWAY`, `OVER_UNDER`, `BOTH_TEAMS_TO_SCORE`, `DOUBLE_CHANCE` |

The Console form pre-fills `sports` with `football`, `dateOffsets` with `0` and `maxItems` with 20. Example input:

```json
{
  "sports": [
    "football"
  ],
  "dateOffsets": [
    0
  ],
  "maxItems": 20
}
```

`leagues` is a local filter on the matches read. Case is ignored and a match is kept when it fits any entry. Each entry takes one of four forms:

- the full header as Flashscore shows it, for example `ALGERIA: Ligue 2`
- the tournament ID, for example `MJijSTCe`
- the tournament path, with or without the slashes, for example `/football/algeria/ligue-2/` or `football/algeria/ligue-2`
- the tournament path without the sport, for example `algeria/ligue-2`

`oddsMarkets` accepts `HOME_DRAW_AWAY`, `HOME_AWAY`, `OVER_UNDER`, `BOTH_TEAMS_TO_SCORE`, `DOUBLE_CHANCE`, `DRAW_NO_BET`, `ODD_OR_EVEN`, `CORRECT_SCORE`, `HALF_FULL_TIME`, `ASIAN_HANDICAP`, `EUROPEAN_HANDICAP` and `ALL`. It is used only when `includeOdds` is on. The default keeps the rows light, because one full answer can hold more than 1 700 odds and most of them are correct scores.

Invalid input ends the run successfully with one `BAD_INPUT` row in the `errors` dataset that lists every problem, and no match row.

### Output

Matches go to the default dataset, one row per match. Problems go to a separate `errors` dataset and are never billed. The `OUTPUT` record of the key-value store summarizes the run. The dataset has three views: `overview`, `timeline` and `odds`.

The first 23 columns are the common layout, in this order:

| Field | Type | Meaning |
|---|---|---|
| `source` | string | Always `Flashscore` |
| `matchId` | string | Flashscore match ID, the key of the row |
| `url` | string or null | Flashscore page of the match |
| `sport` | string | `football`, `tennis` or `basketball` |
| `country` | string or null | Country or region of the tournament |
| `tournament` | string or null | Tournament name |
| `tournamentId` | string or null | Flashscore tournament ID |
| `season` | null | Null for rows of the day feed |
| `round` | null | Null for rows of the day feed |
| `startTime` | string or null | ISO 8601 in UTC |
| `status` | string | `scheduled`, `live`, `finished`, `postponed`, `cancelled`, `abandoned`, `interrupted` or `unknown` |
| `statusRaw` | string or null | The status as Flashscore writes it |
| `minute` | null | Null for rows of the day feed |
| `homeTeam`, `awayTeam` | string or null | Team, or player in tennis |
| `homeTeamId`, `awayTeamId` | string or null | Flashscore IDs |
| `homeScore`, `awayScore` | integer or null | Final or current score |
| `homeScoreHalfTime`, `awayScoreHalfTime` | integer or null | Half-time score |
| `winner` | string or null | `home`, `away` or `draw` for a finished match |
| `scrapedAt` | string | Time the row was built, ISO 8601 in UTC |

Then the Flashscore columns:

| Field | Type | Meaning |
|---|---|---|
| `tournamentStageId` | string or null | Flashscore ID of the tournament stage |
| `tournamentCategory` | string or null | Category of the tournament, such as the tennis tour or the country group |
| `tournamentUrl` | string or null | Flashscore page of the tournament |
| `countryId` | string or null | Flashscore ID of the country or category group |
| `periodScores` | array | Score of each period (half, set or quarter), objects `{ period, home, away }`. `[]` when absent |
| `note` | string or null | Note of Flashscore on the match, for example a disqualification |
| `updatedAt` | string or null | Last update of the match on Flashscore, ISO 8601 in UTC |
| `timeline` | array or null | Incidents of a football match, see below |
| `odds` | array or null | Betting odds, see below |

Every field is always present, with `null` when Flashscore gives no value, and `[]` for `periodScores` when absent.

This is one real row from the example input above, built by the Actor from stored Flashscore responses:

```json
{
  "source": "Flashscore",
  "matchId": "K0CceoUN",
  "url": "https://www.flashscore.com/match/K0CceoUN/",
  "sport": "football",
  "country": "Algeria",
  "tournament": "Ligue 2",
  "tournamentId": "MJijSTCe",
  "season": null,
  "round": null,
  "startTime": "2026-11-06T14:00:00.000Z",
  "status": "finished",
  "statusRaw": "Finished",
  "minute": null,
  "homeTeam": "Azazga",
  "homeTeamId": "p8oVs3AG",
  "awayTeam": "MSP Batna",
  "awayTeamId": "21SPlgRt",
  "homeScore": 3,
  "awayScore": 2,
  "homeScoreHalfTime": 1,
  "awayScoreHalfTime": 1,
  "winner": "home",
  "scrapedAt": "2026-11-07T12:00:00.000Z",
  "tournamentStageId": "dtbtP8nA",
  "tournamentCategory": "Algeria",
  "tournamentUrl": "https://www.flashscore.com/football/algeria/ligue-2/",
  "countryId": "18",
  "periodScores": [
    {
      "period": 1,
      "home": 1,
      "away": 1
    },
    {
      "period": 2,
      "home": 2,
      "away": 1
    }
  ],
  "note": null,
  "updatedAt": "2026-11-06T15:57:32.000Z",
  "timeline": null,
  "odds": null
}
```

The example comes from a test fixture captured on 2026-11-06, so the scores and statuses are those of that day.

#### Timeline

Set `includeTimeline` to `true` to fill `timeline` for football matches that have started. Each element is one incident:

| Field | Meaning |
|---|---|
| `id` | Incident ID |
| `period` | Period name, for example `1st Half` |
| `side` | `home` or `away` |
| `minute`, `addedTime`, `minuteText` | Minute, added time and the minute as Flashscore shows it |
| `type`, `typeCode` | Incident type, such as `Goal`, `Yellow Card` or `Substitution - Out`, and its code |
| `player`, `playerId`, `playerUrl` | The player concerned |
| `reason` | Reason, when Flashscore gives one |
| `homeScore`, `awayScore` | Score after the incident, when it applies |
| `related` | Linked players, such as the assist of a goal or the player coming on |

```json
{
  "id": "vBqDdB2c",
  "period": "1st Half",
  "side": "home",
  "minute": 43,
  "addedTime": null,
  "minuteText": "43'",
  "type": "Goal",
  "typeCode": 3,
  "player": "Orsini N.",
  "playerId": "UyqTS9An",
  "playerUrl": "https://www.flashscore.com/player/orsini-nicolas/UyqTS9An/",
  "reason": null,
  "homeScore": 1,
  "awayScore": 0,
  "related": [
    {
      "type": "Assistance",
      "player": "Bogarin R.",
      "playerId": "tpCGHIpe",
      "playerUrl": "https://www.flashscore.com/player/bogarin-rodrigo/tpCGHIpe/"
    }
  ]
}
```

- `timeline` is `[]` for a football match that is scheduled, postponed or cancelled, and no request is made for it.
- `timeline` is `null` when you did not ask for it, for tennis and basketball rows, and when the summary of the match could not be read.

#### Odds

Set `includeOdds` to `true` to fill `odds` for every match that is not cancelled, postponed or abandoned. Each element is one price of one operator:

| Field | Meaning |
|---|---|
| `bookmakerId`, `bookmaker` | Operator ID and name. The name is null when Flashscore does not list it |
| `market` | Market, such as `HOME_DRAW_AWAY` or `OVER_UNDER` |
| `scope` | Part of the match the price covers, such as `FULL_TIME` or `FIRST_HALF` |
| `outcome` | `home`, `draw` or `away` for the result markets, `home_or_draw`, `draw_or_away` or `home_or_away` for double chance. Null for the other markets |
| `participant` | `home` or `away` when the price belongs to one side |
| `selection` | The selection of the market, for example `OVER` for an over/under price |
| `handicap` | The line, as a number, for handicap and over/under markets |
| `winner` | Raw text of the source for a half-time/full-time price, for example `1/1` |
| `score`, `bothTeamsToScore` | Set for correct score and both-teams-to-score prices |
| `odds`, `openingOdds` | Current and opening decimal odds. `openingOdds` is null when there is no opening price |
| `active` | Whether the price is still open |

```json
{
  "bookmakerId": 14,
  "bookmaker": "10bet",
  "market": "HOME_DRAW_AWAY",
  "scope": "FIRST_HALF",
  "outcome": "home",
  "participant": "home",
  "selection": null,
  "handicap": null,
  "winner": null,
  "score": null,
  "bothTeamsToScore": null,
  "odds": 3.95,
  "openingOdds": 3.2,
  "active": false
}
```

- `odds` is `[]` when no price was kept by your `oddsMarkets` choice, or when no operator shows a price.
- `odds` is `null` when you did not ask for it, for a cancelled, postponed or abandoned match, and when the odds could not be read.
- Operators without prices give nothing and bonus offers are never returned.

### Billing

One `match` event is billed per delivered row. One `match-timeline` event is billed on top for a row delivered with a non-empty timeline, and one `match-odds` event for a row delivered with at least one odds element. Errors, duplicates, filtered-out matches, empty timelines and empty odds are never billed. The Pricing tab of the Actor shows the current prices.

When the platform moves a run to another server or restarts it while it works, the run picks up where it was. A match already returned is neither returned nor billed again.

### Limits

- `season`, `round` and `minute` are null for the rows of the day feed.
- The day is the day of Flashscore. The feed covers 7 days before to 7 days after today (UTC). `dates` are added to `dateOffsets`, so today is part of the selection only when `dateOffsets` holds 0, which the Console form pre-fills.
- A match close to midnight can appear in two day feeds. It is returned once.
- The timeline exists for football only. Tennis and basketball rows keep `timeline` null.
- Odds are seen from the United Kingdom: the operators and markets are those shown there. Finished matches carry closing odds. Basketball matches often have no odds.
- `oddsMarkets` needs at least one market when `includeOdds` is on.
- If the day feed answers 401, the run records a `TARGET_BLOCKED` row with `httpStatus` 401, which means the feed signature was refused.
- If the host of the odds or the summary host refuses three requests in a row, only that option stops. The rows already read are kept, the option stays null for the rest of the run, and `oddsStoppedBy` or `timelineStoppedBy` says `blocked` in the `OUTPUT` record.
- A run with both options sends about two more requests per match than a run without them, so it lasts longer. For large runs, raise the run timeout.
- Each run is a snapshot of Flashscore at that moment. Scores and odds move, so two runs a minute apart can differ.

### Run summary and early stops

The `OUTPUT` record counts requests, pages, matches, errors and the add-ons: `timelinesEmitted`, `timelinesCharged`, `timelineRequests`, `oddsEmitted`, `oddsCharged` and `oddsRequests`. `extraByEvent` gives the emitted and charged count of each add-on event.

`stoppedBy` says why a run ended early. It keeps the first reason only.

- `null`: all the requested work was done.
- `maxItems`: the `maxItems` cap was reached.
- `chargeLimit`: the maximum charge set for the run was reached.
- `deadline`: the run reached its internal time limit.
- `circuitOpen`: too many blocked responses in a row.
- `pageBudget`: the request budget of the run was spent.
- `badInput`: reserved value of the run summary. This Actor never sets it, because Flashscore answers are sorted into blocked, rate limited, not found or failed. An input the Actor cannot use is reported before any request, as a single `BAD_INPUT` row in the `errors` dataset, and `stoppedBy` stays `null`.
- `fetchFailed`: the last walk ended on a page that could not be fetched. `failedWalks` lists the first 20 failed walks.

Every business error is written to the `errors` dataset with a `kind`: `BAD_INPUT`, `TARGET_BLOCKED`, `RATE_LIMITED`, `FETCH_FAILED`, `UPSTREAM_ERROR`, `SCHEMA_CHANGED`, `LISTING_INVALID` or `NOT_FOUND`. The `OUTPUT` record counts errors in `errorsByKind`.

### Frequently asked questions

#### Why do I get fewer matches than `maxItems`?

The days and sports you chose hold fewer matches than the cap, or your `leagues` narrow the list. When Flashscore has no more matching matches, the run ends with `stoppedBy` set to `null`.

#### Why is `odds` empty on some rows?

Basketball matches often have no odds from the operators listed in the United Kingdom, and a cancelled, postponed or abandoned match gets no odds request. Check `odds` for `null` (not obtained) and `[]` (nothing kept).

#### Is there a similar Actor for another site?

Yes, [SofaScore Sports Scraper](https://apify.com/mrbridge/sofascore-sports-scraper) returns the same 23 first columns, so rows from both sources fit in one table. SofaScore adds the venue, the referee, the managers and the line-ups, but no odds. Both return a football timeline, only this Actor returns odds.

#### Can I use Flashscore Matches Scraper via MCP?

Yes. Any Apify Actor can be called from an MCP client through the Apify MCP server. See the [Apify MCP documentation](https://docs.apify.com/platform/integrations/mcp) for the setup.

#### Your feedback

We are always working on improving our Actors. If you have technical feedback for Flashscore Matches Scraper or found a bug, please create an issue on the Actor's [Issues tab](https://apify.com/mrbridge/flashscore-matches-scraper/issues).

More free tools, scrapers, MCP servers and data studies from the same team: [mr-bridge.com](https://mr-bridge.com).

### Legal and responsible use

The data comes from the public pages of Flashscore (https://www.flashscore.com) and belongs to its owners. Flashscore has its own terms of use, and it may restrict automated access or commercial reuse. Odds are given as information and are not betting advice. Before you rely on the output, in particular for commercial use, confirm that your use is permitted by the terms that apply. You remain responsible for how you use the results and for complying with the source's terms and with the laws that apply to you.

# Changelog

This Actor's version history is a separate document: https://apify.com/mrbridge/flashscore-matches-scraper/changelog.md

# Actor input Schema

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

Sports to read, one feed per sport and day. At least one.

## `dateOffsets` (type: `array`):

Days from today (UTC), as Flashscore counts its days: 0 is today, -1 yesterday, 1 tomorrow. Integers from -7 to 7. Today is part of the selection only when this list holds 0.

## `dates` (type: `array`):

Calendar days as YYYY-MM-DD, within 7 days of today (UTC). Added to the day offsets.

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

Keep only these tournaments, up to 100, case ignored. Each entry is a full header (ALGERIA: Ligue 2), a tournament ID (MJijSTCe), a path with or without slashes (football/algeria/ligue-2) or a path without the sport (algeria/ligue-2). Empty: all tournaments.

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

Hard cap on the match rows returned and billed, from 1 to 10000. The run stops once that many rows are delivered.

## `includeTimeline` (type: `boolean`):

For each football match that has started, open its summary and add the timeline of incidents (goals, assists, cards, substitutions) in the timeline column. A match whose timeline has at least one incident is billed one extra match-timeline event. Matches not played yet, postponed or cancelled get an empty timeline and no request. Tennis and basketball rows are not changed (timeline null). A summary that cannot be read leaves timeline null, the match row is still returned and not billed that event. This option adds one request per football match: for large runs, raise the run timeout.

## `includeOdds` (type: `boolean`):

For each match (football, tennis, basketball), read the betting odds of the operators listed in the United Kingdom and add them to the odds column: operator, market, scope, outcome, line, current and opening decimal odds. A match with at least one odds element is billed one extra match-odds event. Cancelled, postponed and abandoned matches get no request (odds null). Odds that cannot be read leave odds null, the match row is still returned and not billed that event. This option adds one request per match: for large runs, raise the run timeout.

## `oddsMarkets` (type: `array`):

Markets kept in the odds column when the odds add-on is on. At least one. ALL keeps every market. A full answer can hold more than 1 700 odds, most of them correct scores: the default keeps the row light.

## `internalCaptureMode` (type: `boolean`):

Maintainer only. Not for normal use: runs the capture plan named below instead of the scraper.

## `capturePlan` (type: `string`):

Maintainer only. Name of a closed capture campaign (flashscore-2026-11, flashscore-2026-11b or flashscore-2026-11c). Requires Internal capture mode. The allowlist lives in the code: any other value is rejected without issuing a request.

## Actor input object example

```json
{
  "sports": [
    "football"
  ],
  "dateOffsets": [
    0
  ],
  "maxItems": 20,
  "includeTimeline": false,
  "includeOdds": false,
  "oddsMarkets": [
    "HOME_DRAW_AWAY",
    "HOME_AWAY",
    "OVER_UNDER",
    "BOTH_TEAMS_TO_SCORE",
    "DOUBLE_CHANCE"
  ],
  "internalCaptureMode": false
}
```

# Actor output Schema

## `matches` (type: `string`):

Direct link to all dataset items produced by the run.

## `summary` (type: `string`):

Direct link to the OUTPUT record: requests made, matches read, returned and billed, add-on counts, errors by kind and the reason of an early stop.

# 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 = {
    "sports": [
        "football"
    ],
    "dateOffsets": [
        0
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("mrbridge/flashscore-matches-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 = {
    "sports": ["football"],
    "dateOffsets": [0],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("mrbridge/flashscore-matches-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 '{
  "sports": [
    "football"
  ],
  "dateOffsets": [
    0
  ],
  "maxItems": 20
}' |
apify call mrbridge/flashscore-matches-scraper --silent --output-dataset

```

## MCP server setup

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