# Flashscore Tennis Scraper — Matches, Stats, Points & H2H (`steadyscrape/flashscore-tennis-scraper`) Actor

Every tennis match of a day from Flashscore — ATP, WTA, Challenger, ITF, doubles, Davis Cup — with scores, players, rankings and tournament. Add match statistics, point-by-point and head-to-head for the whole day in the same run. Filter by player, tournament, tour or status. No browser, no login.

- **URL**: https://apify.com/steadyscrape/flashscore-tennis-scraper.md
- **Developed by:** [Steady Scrape](https://apify.com/steadyscrape) (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 matches

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?

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 Tennis Scraper — matches, odds, statistics, point by point and head-to-head

Get **every tennis match of a day from Flashscore** as clean JSON, CSV or Excel: ATP, WTA, Challenger, ITF, doubles and
team ties (Davis Cup, Billie Jean King Cup), with scores, players, countries, tournament, surface and status. Turn on
the detail sections and the **same run** adds, for every match, its **betting odds** from every bookmaker with their
opening prices, its **statistics**, its **point-by-point** history, its **head-to-head** with recent form, and the
players' **full names and ATP/WTA rankings**.

No browser, no login, no proxy to pay for. A whole day of tennis is one request; a day with every detail is a few
seconds per dozen matches.

### What makes it different

- **A day with details is one run.** You do not list matches in one run and then start another run per match to get
  its points: tick *Point by point* and every match of the day comes back with them.
- **Filter the day by player.** `players: ["Alcaraz"]` — case and accents do not matter, so `garcia` finds *García*.
- **Relative days for schedules.** `dateFrom: "yesterday"` means yesterday every day the schedule fires. Dates and
  offsets (`-3`, `+2`) work too, up to a week either side of today, and `utcOffsetHours` cuts the day at your clock.
- **Numbers you can use.** Flashscore writes `80% (44/55)`; you get `value: 80`, `won: 44`, `total: 55` next to the text.
- **Points that mean something.** Every game carries its server, its winner, whether serve was broken, and each point
  flagged as a break point, set point or match point. Tiebreaks come point by point.
- **Odds from the bookmakers of the country you choose.** `oddsCountry: "GB"` gives about twenty UK bookmakers,
  `"US"` with `oddsRegion: "USNY"` gives FanDuel, DraftKings, BetMGM and Fanatics. Every price comes with its opening
  price, so line movement is in the row; markets: winner of the match and of each set, over/under, Asian handicap,
  correct score, odd/even.
- **Head-to-head before the match starts**, which is when a model needs it: previous meetings and each side's last
  matches with surface, score and winner.
- **You pay for what you get.** A match without details is one price, a match that delivers details another. Rows that
  say "no match", "not found" or "blocked" are free, and a section Flashscore does not have for a match is not charged.
- **It fails loudly when it should.** If Flashscore changes its feed the run fails with a clear message instead of
  handing you an empty day that looks like a quiet one.

### Use cases

- **Betting and trading models**: today's scheduled matches with every bookmaker's price and opening price,
  head-to-head and recent form; yesterday's results with serve and return statistics every morning.
- **Odds comparison and line-movement tracking**: schedule it and compare `odds` with `openingOdds` across bookmakers.
- **Live dashboards**: run every few minutes with `matchStatus: ["live"]` for the set in play, who serves and the
  points of the game in play.
- **Player and tournament tracking**: follow a player, a tournament or a tour with the filters.
- **Research and content**: point-by-point histories for momentum studies, break-point analysis, match reports.

### Input

| field | what it does |
|---|---|
| `dateFrom`, `dateTo` | `today`, `yesterday`, `tomorrow`, `-3`, `+2` or `2026-09-19`. `dateTo` defaults to `dateFrom`. Within 7 days of today. |
| `utcOffsetHours` | Where a day starts and ends (`0` = UTC, `-5` = New York). Times in the output are always UTC. |
| `matches` | Match links or ids instead of days. |
| `matchStatus` | `scheduled`, `live`, `finished` (includes retirements and walkovers), `cancelled` (includes postponed). |
| `tours` | `atp`, `wta`, `challenger_men`, `challenger_women`, `itf_men`, `itf_women`, `teams`, `juniors`, `exhibition`, `other`. |
| `matchTypes` | `singles`, `doubles`, `teams`. |
| `tournaments`, `players` | Part of a name; case and accents do not matter. |
| `includeStatistics`, `includePointByPoint`, `includeHeadToHead`, `includeOdds` | The detail sections. Any of them also adds full names and rankings. |
| `oddsCountry`, `oddsRegion`, `oddsMarkets` | Whose bookmakers (`GB` by default, `US` + `USNY`…) and which markets: `winner` by default, `over_under`, `asian_handicap`, `correct_score`, `odd_even`; empty for all. |
| `recentMatchesPerPlayer` | How many last matches per side in the head-to-head (default 10, up to 50). |
| `maxMatches` | Stop after this many matches, earliest first. |

Yesterday's finished ATP and WTA singles with statistics:

```json
{ "dateFrom": "yesterday", "matchStatus": ["finished"], "tours": ["atp", "wta"], "matchTypes": ["singles"], "includeStatistics": true }
```

Today's scheduled ATP and WTA matches with the UK bookmakers' winner prices:

```json
{ "dateFrom": "today", "matchStatus": ["scheduled"], "tours": ["atp", "wta"], "includeOdds": true }
```

Today's matches of two players, with head-to-head, before they start:

```json
{ "dateFrom": "today", "players": ["Sinner", "Swiatek"], "includeHeadToHead": true }
```

Everything live right now:

```json
{ "dateFrom": "today", "matchStatus": ["live"] }
```

One match, with everything:

```json
{ "matches": ["https://www.flashscore.com/match/bXhRwVq3/"], "includeStatistics": true, "includePointByPoint": true, "includeHeadToHead": true }
```

### Output

One row per match (shortened here):

```json
{
  "status": "ok",
  "matchId": "bXhRwVq3",
  "url": "https://www.flashscore.com/match/bXhRwVq3/",
  "startTime": "2026-09-18T12:20:00.000Z",
  "updatedAt": "2026-09-18T14:32:44.000Z",
  "matchStatus": "finished",
  "matchStatusText": "Finished",
  "tournament": { "name": "Davis Cup - World Group I", "category": "ATP - Singles", "tour": "atp", "matchType": "singles", "country": "World", "surface": null, "indoor": false, "qualification": false, "url": "https://www.flashscore.com/tennis/atp-singles/davis-cup-world-group-i/" },
  "home": {
    "name": "Van De Zandschulp B.",
    "players": [{ "name": "Van De Zandschulp B.", "fullName": "Botic Van De Zandschulp", "country": "Netherlands", "id": "YwiLpILD", "url": "https://www.flashscore.com/player/van-de-zandschulp-botic/YwiLpILD/", "ranking": { "list": "ATP", "position": 40 } }],
    "setsWon": 2, "isWinner": true, "currentGamePoints": null
  },
  "away": { "name": "Rodriguez J. A.", "players": [{ "fullName": "Johan Alexander Rodriguez", "country": "Colombia", "ranking": { "list": "ATP", "position": 829 } }], "setsWon": 1, "isWinner": false },
  "winner": "home",
  "sets": [
    { "set": 1, "home": 4, "away": 6, "homeTiebreak": null, "awayTiebreak": null, "durationMinutes": 49 },
    { "set": 2, "home": 6, "away": 1, "homeTiebreak": null, "awayTiebreak": null, "durationMinutes": 37 },
    { "set": 3, "home": 7, "away": 5, "homeTiebreak": null, "awayTiebreak": null, "durationMinutes": 48 }
  ],
  "durationMinutes": 134,
  "statistics": [
    { "period": "match", "group": "Service", "name": "1st serve points won", "home": { "text": "80% (44/55)", "value": 80, "won": 44, "total": 55 }, "away": { "text": "62% (40/65)", "value": 62, "won": 40, "total": 65 } }
  ],
  "pointByPoint": [
    { "set": 1, "game": 3, "tiebreak": false, "server": "away", "winner": "home", "serviceBreak": true, "homeScore": 2, "awayScore": 1,
      "points": [{ "home": "30", "away": "30", "breakPoint": false, "setPoint": false, "matchPoint": false }, { "home": "40", "away": "30", "breakPoint": true, "setPoint": false, "matchPoint": false }] }
  ],
  "headToHead": {
    "meetings": [{ "matchId": "bXhRwVq3", "startTime": "2026-09-18T12:20:00.000Z", "tournament": "Davis Cup - World Group I", "surface": null, "home": "Van De Zandschulp B.", "away": "Rodriguez J. A.", "score": "2:1", "winner": "home", "url": "https://www.flashscore.com/match/bXhRwVq3/" }],
    "homeRecent": [{ "matchId": "pIjwuyeL", "tournament": "US Open", "surface": "hard", "home": "Zverev A.", "away": "Van De Zandschulp B.", "score": "3:0", "winner": "home" }],
    "awayRecent": []
  },
  "odds": [
    { "bookmakerId": 263, "bookmaker": "BetUK", "market": "winner", "scope": "match",
      "selections": [
        { "side": "home", "selection": null, "line": null, "lineUnit": null, "score": null, "odds": 1.01, "openingOdds": 1.01, "active": true },
        { "side": "away", "selection": null, "line": null, "lineUnit": null, "score": null, "odds": 11, "openingOdds": 10.5, "active": true }
      ] }
  ],
  "scrapedAt": "2026-09-20T12:00:00.000Z"
}
```

| field | meaning |
|---|---|
| `status`, `reason` | `ok` for a match. `no_matches`, `not_found` and `blocked` rows are free and say why in `reason`. On an `ok` row, `reason` names a section that could not be fetched. |
| `matchStatus` | `scheduled`, `delayed`, `live`, `interrupted`, `awaiting_updates`, `to_finish`, `between_days`, `finished`, `retired`, `walkover`, `awarded`, `postponed`, `cancelled`, `abandoned`. `matchStatusText` is Flashscore's wording ("Set 3", "Finished / retired"). |
| `liveSet`, `serving`, `currentGamePoints` | For a match in play: the set on, who serves, and the points of the game (`0`, `15`, `30`, `40`, `A`). |
| `sets` | Games per set, tiebreak points when there was one, and the set's length in minutes (with details). |
| `home`, `away` | The side's name, its players (two for doubles) with country, Flashscore id and page; full name and ranking with details. |
| `statistics` | Per period (`match`, `set_1`…), group and name, each side with `text`, `value`, `won`, `total`. `null` when not asked for, `[]` when Flashscore has none for the match. |
| `pointByPoint` | Every game in order; a tiebreak is one game whose points are the tiebreak's running score. |
| `headToHead` | `meetings`, `homeRecent`, `awayRecent`. |
| `odds` | One entry per bookmaker, `market` (`winner`, `over_under`, `asian_handicap`, `correct_score`, `odd_even`) and `scope` (`match`, `set_1`…). Each selection has decimal `odds` and `openingOdds`, and what it is for: `side` (winner, handicap), `selection` + `line` + `lineUnit` (over/under: `over 22.5 games`; odd/even: `odd`), `score` (correct score, home first). `active: false` is a suspended price. |

A run summary (rows per status, rows with details, requests made) is saved as `SUMMARY` in the key-value store.

### Pricing

Pay per event, nothing else: platform usage is included.

- **`match`** — a match row: **US$0.001** (US$1 per 1,000 matches).
- **`match-with-details`** — a match row that delivers odds, statistics, point by point or head-to-head: **US$0.004**,
  whatever the number of sections.

A full day of tennis (about 250 matches) costs about US$0.25 as a list. Rows with any status other than `ok` are free,
and so is a detail section Flashscore does not have for a match.

### Limits

- Flashscore publishes days from 7 days ago to 7 days ahead. For an older match, give its link in `matches`.
- Statistics and point by point exist where Flashscore covers a match at that level. Measured on a full day
  (2026-09-19, 187 finished matches): **statistics for every WTA and Challenger match** and for the ATP-level matches
  of the day except the lowest Davis Cup group, none for ITF matches; **point by point for nearly every tour and
  Challenger match and about half of the ITF ones**; head-to-head for all but one. A section Flashscore does not have
  comes back empty and uncharged.
- Odds are pre-match prices as Flashscore's odds comparison shows them; bookmakers take them down some time after a
  match ends. Odds are decimal.
- A run reads what Flashscore shows at that moment; it is not a stream. Schedule it as often as you need.

### Is it legal?

The Actor reads pages and feeds that Flashscore serves publicly, without logging in, and collects sports results and
statistics, not personal data beyond the names of professional players as they appear in public results. You are
responsible for how you use the data; check the terms that apply to you and your jurisdiction.

# Actor input Schema

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

<code>today</code>, <code>yesterday</code>, <code>tomorrow</code>, a number of days from today (<code>-3</code>, <code>+2</code>) or a date (<code>2026-09-19</code>). Relative days are what a scheduled run wants. Flashscore publishes days up to a week either side of today.

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

Last day of the range, written like <b>From day</b>. Leave empty for a single day.

## `utcOffsetHours` (type: `integer`):

Where a day starts and ends: <code>0</code> cuts days at UTC, <code>-5</code> at New York time, <code>2</code> at Central European summer time. Match times in the output are always UTC.

## `matches` (type: `array`):

Flashscore match links, or the eight-character match ids the output gives as <code>matchId</code>. When this list is not empty the days and the filters are ignored.

## `matchStatus` (type: `array`):

Leave empty for all. <b>finished</b> includes retirements and walkovers; <b>live</b> includes interrupted matches; <b>cancelled</b> includes postponed ones.

## `tours` (type: `array`):

Leave empty for all.

## `matchTypes` (type: `array`):

Leave empty for all.

## `tournaments` (type: `array`):

Part of a tournament name: <code>Wimbledon</code>, <code>Roland Garros</code>, <code>M15 Monastir</code>. Case and accents do not matter.

## `players` (type: `array`):

Part of a player name as Flashscore lists it, usually the surname: <code>Alcaraz</code>, <code>Swiatek</code>. Case and accents do not matter.

## `includeStatistics` (type: `boolean`):

Aces, double faults, serve and return percentages, break points, points and games won — for the match and for each set, each with its number and its fraction (<code>80% (44/55)</code> → 80, 44, 55).

## `includePointByPoint` (type: `boolean`):

Every game with its server, winner, breaks of serve and the score after each point, with break points, set points and match points marked; tiebreaks point by point.

## `includeHeadToHead` (type: `boolean`):

Previous meetings of the two sides and the last matches of each, with surface, score and winner. Available before the match starts, which is when a model needs it.

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

Every bookmaker's prices for the match, each with its <b>opening price</b>: match winner, set winner, over/under, Asian handicap, correct score and odd/even. Available before the match starts.

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

Two-letter country code: Flashscore lists the bookmakers licensed there. <code>GB</code> has the most (about 20: bet365, William Hill, Ladbrokes, Betfair…); <code>US</code> has FanDuel, DraftKings and BetMGM; try <code>DE</code>, <code>IT</code>, <code>BR</code>, <code>AU</code>… An unknown code gets an international set.

## `oddsRegion` (type: `string`):

For countries that license by state. <code>USNY</code> adds the bookmakers of New York to those of the US. Leave empty otherwise.

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

Which markets to keep. Leave empty for all of them.

## `recentMatchesPerPlayer` (type: `integer`):

How many of each side's last matches the head-to-head carries.

## `maxMatches` (type: `integer`):

Stops after this many matches, earliest first. <code>0</code> for no limit. Your run's maximum charge is respected either way.

## Actor input object example

```json
{
  "dateFrom": "yesterday",
  "utcOffsetHours": 0,
  "matchStatus": [
    "finished"
  ],
  "tours": [
    "atp",
    "wta",
    "challenger_men",
    "challenger_women"
  ],
  "matchTypes": [
    "singles"
  ],
  "includeStatistics": true,
  "includePointByPoint": false,
  "includeHeadToHead": false,
  "includeOdds": false,
  "oddsCountry": "GB",
  "oddsMarkets": [
    "winner"
  ],
  "recentMatchesPerPlayer": 10,
  "maxMatches": 10
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "dateFrom": "yesterday",
    "matchStatus": [
        "finished"
    ],
    "tours": [
        "atp",
        "wta",
        "challenger_men",
        "challenger_women"
    ],
    "matchTypes": [
        "singles"
    ],
    "includeStatistics": true,
    "maxMatches": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/flashscore-tennis-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 = {
    "dateFrom": "yesterday",
    "matchStatus": ["finished"],
    "tours": [
        "atp",
        "wta",
        "challenger_men",
        "challenger_women",
    ],
    "matchTypes": ["singles"],
    "includeStatistics": True,
    "maxMatches": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/flashscore-tennis-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 '{
  "dateFrom": "yesterday",
  "matchStatus": [
    "finished"
  ],
  "tours": [
    "atp",
    "wta",
    "challenger_men",
    "challenger_women"
  ],
  "matchTypes": [
    "singles"
  ],
  "includeStatistics": true,
  "maxMatches": 10
}' |
apify call steadyscrape/flashscore-tennis-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyscrape/flashscore-tennis-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/hpyMzuPfFPjcOOnJo/builds/2aMrHMdTgwO6el5vZ/openapi.json
