# Flashscore Tennis Scraper - Stats, Scores, H2H (`webdata_labs/flashscore-tennis-scraper`) Actor

\[$1/1K matches + $2.20/1K with full stats, no start fee] ATP, WTA, Challenger and ITF match results from Flashscore: aces, double faults, serve speed, winners and unforced errors, set scores with tiebreak points and durations, referee and venue. Covers 7 days back to 7 days ahead.

- **URL**: https://apify.com/webdata\_labs/flashscore-tennis-scraper.md
- **Developed by:** [WebData Labs](https://apify.com/webdata_labs) (community)
- **Categories:** Developer tools, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 match scrapeds

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?

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

## Flashscore Tennis Scraper

**ATP, WTA, Challenger and ITF match results with the statistics most tennis scrapers leave out - aces, double faults, serve speed, set durations and tiebreak points, not just a scoreline.**

Most Flashscore-based tennis Actors hand you the scoreboard: who played, who won, the set score. That is also the free version of the site. The reason to pay for a tennis data feed is the match report Flashscore publishes alongside it - who served how many aces, how long each set actually took, whether a set went to a tiebreak and by how much, who the referee was and where it was played - and most scrapers in this niche do not fetch it. This one does, as a clean row rather than a page you would have to parse yourself.

### ✅ What you get / ❌ what this isn't

| ✅ What you get | ❌ What this isn't |
|---|---|
| **Full match statistics** - aces, double faults, serve speed, winners, unforced errors, net points, service/return points won, by set and for the whole match | Not just a final score with no idea how either player actually played |
| **Set-by-set duration and tiebreak points**, not just games won | Not "7-6" with the tiebreak score thrown away |
| **Referee and venue** on every match with statistics | Not left off the row when Flashscore itself prints them |
| **A derived `winner`, computed from the actual set scores** | Not a status flag that turns out to mean something else on a walkover row |
| **Head-to-head history on request**, by surface, with a win/loss flag already applied | Not a separate page you would have to fetch and parse yourself |
| **One row per match, ready for a spreadsheet or a model** | Not nested nested JSON blocks per section that still need flattening |
| **Live score and current set**, when a match is in progress | Not a scoreboard that only updates once the match is over |
| **Pay only for what you asked for**: a plain row costs one price, adding statistics costs a second, separate charge | Not one flat per-row price whether or not you got the stats |

### 🔎 Why use this Actor

- **Statistics are the differentiator, not an afterthought.** Aces, double faults, first/second serve percentage, serve speed, winners, unforced errors, net points won, and the same numbers broken down set by set - all delivered as a clean list of `{ section, subsection, stat, home, away }` rows, not a page of tables to re-parse.
- **Tiebreaks are captured, not dropped.** A set that went to a breaker carries both players' tiebreak points as `homeTiebreak`/`awayTiebreak`, and the human-readable `scoreLine` renders it the way a scoresheet does: `7-6(7)`.
- **The winner is computed from the score, not trusted from a flag.** Flashscore's own "match decided" marker turns out to appear on roughly half of finished matches - including at least one walkover with no games played at all - so it never tells you which side won. `winner` here is derived by actually counting sets won.
- **Referee and venue are included for free** whenever statistics are fetched, because Flashscore's own match-detail feed carries them right next to the set durations.
- **The +/-7 day window is used honestly.** Flashscore's own API does not serve match data further back or further out than a week either side of today - true of every Actor built on this source - and the input schema clamps to that range with a warning instead of silently returning nothing for a date you asked for outside it.
- **A day that genuinely has no data is told apart from a block.** An out-of-range day offset and a real block both return quickly; the run only ever reports "blocked" when every single request failed, never when a filter or a quiet day is the real reason for an empty dataset.
- **Pay per row delivered.** No start fee. A filtered-out match is never written and never charged, and rows stream into the dataset as they're parsed, so an abort still leaves you what you already paid for.

### 👥 Who it's for

Tennis data journalists, betting and handicapping analysts, fantasy-tennis platforms, and sports researchers who need more than a final score.

- Build a daily results feed with full box-score statistics for a tennis newsletter or site.
- Feed serve speed, break-point conversion and unforced-error rates into a handicapping or prop-betting model.
- Track a player's recent form and head-to-head record before a big match.
- Score a fantasy-tennis league from aces, winners and games won.
- Watch a live match's current set and game score without refreshing Flashscore yourself.

### Example tasks

- [Get today's ATP and WTA match results with full statistics](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/tennis-results-today-with-stats)
- [Scrape live tennis scores in progress](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/live-tennis-scores)
- [Track ATP tour match statistics](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/atp-tour-match-statistics)
- [Track WTA tour match statistics](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/wta-tour-match-statistics)
- [Get a Grand Slam tournament's results](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/grand-slam-tournament-results)
- [Follow one player's matches and head-to-head record](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/tennis-player-match-tracker)
- [Get upcoming tennis matches for the week ahead](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/upcoming-tennis-matches-this-week)
- [Daily tennis results monitor (schedulable)](https://apify.com/webdata_labs/flashscore-tennis-scraper/examples/daily-tennis-results-monitor)

### ⚙️ How to scrape tennis match data

1. Leave **Which days to scan** at `[0]` for today, or list any offsets from -7 to 7 (negative for past days, positive for upcoming).
2. Narrow it with **Match status** (scheduled, live or finished), **Tour**, **Tournament name** or **Player name** - all plain text matches, so "ATP" or "Djokovic" work as typed.
3. Leave **Include match statistics and set detail** on for the full box score - aces, double faults, serve speed, tiebreak points, set durations, referee and venue. Turn it off for a fast, cheap scoreline-only run.
4. Turn on **Include head-to-head history** if you want each match's recent-form and head-to-head context. It is a much bigger fetch, so it is off by default.
5. Click **Start**. Rows appear in the dataset as each match is parsed. Export from the **Output** tab as JSON, CSV, Excel, XML or an API feed.

#### Get a daily results feed

Save a task with **Which days to scan** set to `[0]` and **Match status** set to "Finished", then schedule it daily. Each run delivers that day's completed matches with full statistics - a ready-made box-score feed for a results page or newsletter.

#### Watch a live match

Set **Match status** to "Live" and run it every few minutes. Each row carries `liveScore` with the current set and game points, so you can build a simple live-scores page without polling Flashscore's own site.

### 📥 Input

```json
{
  "days": [0],
  "status": "finished",
  "tour": "ATP",
  "includeStatistics": true,
  "maxResults": 50
}
```

- `days` - day offsets from today, `0` = today, negative = past, positive = upcoming. Flashscore only serves -7 to 7; anything outside that is ignored with a warning. Default `[0]`.
- `status` - `all`, `scheduled`, `live` or `finished`. Default `all`.
- `tour` - optional text match against the competition category, for example `ATP`, `WTA`, `Challenger` or `ITF`. Not a fixed list - Flashscore's own category labels are not a small closed set, so this is a substring match rather than a dropdown that could silently exclude a valid value.
- `tournamentName` - optional text match against the tournament name, for example `US Open`.
- `playerName` - optional text match against either player's name.
- `matchIds` - specific Flashscore match IDs to keep, one per line. Still applied as a filter on top of `days` - a match outside the requested day range will not be found even if listed here.
- `includeStatistics` - fetch full match statistics and set detail (duration, tiebreak points, referee, venue). Default `true`. This is what triggers the second "statistics" charge - see pricing below.
- `includeHeadToHead` - add recent head-to-head history by surface. Default `false`, because it is a much larger fetch (roughly 150 KB per match). Billed under the same statistics charge as `includeStatistics`, no separate fee.
- `maxResults` - stop after this many rows. Default 20, `0` for unlimited.

### 📤 Output

One row per match:

| tournamentName | tour | surface | status | homePlayerName | awayPlayerName | scoreLine | winner |
|---|---|---|---|---|---|---|---|
| US Open (USA) | ATP - Singles | hard | finished | Tiafoe F. | Shelton B. | 6-4, 3-6, 3-6, 5-7 | away |
| US Open (USA) | WTA - Singles | hard | live | Kichenok N. | Melichar-Martinez N. | 6-4, 3-6 | |

Each row also carries `matchId`, `matchUrl` (the public Flashscore page), `startTime`/`finishedTime` (ISO), both players' `slug`, `playerId` and `country`, the full `sets` array (games, and when statistics are on, duration and tiebreak points per set), `liveScore` (current set and game points, live matches only), and `dayOffset`. When `includeStatistics` is on, rows also carry `statistics` (a list of `{ section, subsection, stat, home, away }` rows), `matchDurationMinutes`, `referee`, `refereeCountry`, `venue`, `city`, `hasStatistics` and `hasHeadToHead`. When `includeHeadToHead` is on, rows carry `headToHead`, a list of the reference player's recent matches by surface group.

### 💵 How much does it cost?

Two charges, no start fee. Every delivered match costs a **match row** ($1.00/1K at FREE, down to $0.85/1K at GOLD and above); if `includeStatistics` (or `includeHeadToHead`) successfully returned data for that match, it also costs a **statistics** charge ($2.80/1K at FREE, down to $2.20/1K at GOLD and above). A scoreline-only run (`includeStatistics: false`) only ever pays the first charge. A typical day across the ATP and WTA tours is 100-250 matches; a single tournament's day is usually 10-40.

### 🔁 Run it on the Apify platform

Schedule it (daily results, or every few minutes for a live board), call it from the REST API or the JavaScript and Python clients, or wire the dataset into Make, Zapier, n8n, Google Sheets, Slack or a webhook. Request pacing and retries are handled internally and are included in the price - there is nothing to configure, and no proxy is needed for this source.

### ⚠️ Limits and caveats

- **The day window is exactly -7 to +7 days from today, on every run.** This is a limit of Flashscore's own feed, not this Actor, and it applies to every Actor built on this source - there is no deeper archive to unlock with a different input. If you need results from further back, you will need a different source or your own historical capture going forward.
- **Not every match carries statistics, even with `includeStatistics` on.** Lower-tier matches (qualifying rounds, some ITF and Challenger matches) sometimes have no statistics published on Flashscore at all. `hasStatistics: false` tells you the fetch ran and came back empty - it is a real gap on the source, not a failed request.
- **`liveScore.setInProgress` is best-effort.** It is derived from a single field Flashscore does not document, confirmed against one live sample. Treat it as a strong hint, not a guaranteed set number, until more live matches have confirmed it.
- **Sets 4 and 5 use the same key pattern as sets 1-3 by extrapolation.** Every match sampled during development went to at most 4 sets; the pattern for a 5th set has not been directly observed. If a 5-set match's final set ever comes back empty, please open an issue with the run URL.
- **`tour` and `tournamentName` are text matches, not a fixed dropdown**, because Flashscore's own category labels are not a small closed set we could safely enumerate. A typo or an unusual tour name will simply match nothing rather than error.
- **This Actor reads Flashscore's tennis feed only.** It does not cross-reference ATP/WTA's own sites, so a player's official ranking or a tournament's prize money are not included.

### 🧩 Related Actors

- [UFC Scraper](https://apify.com/webdata_labs/ufc-scraper) - the same normalized-row approach applied to fighters, fights, events and rankings.
- [Polymarket & Kalshi Scraper](https://apify.com/webdata_labs/prediction-markets-scraper) - prediction-market odds as a forward-looking signal to pair with match statistics.

### ❓ FAQ

#### Am I charged for matches a filter removes?

No. Filtered-out matches are never written to the dataset and never charged. You pay only for rows delivered.

#### Why can't I get last month's results?

Flashscore's own feed only serves a week either side of today - this Actor is not artificially limiting it, that is the actual depth of the source. Day offsets outside -7..7 are dropped with a warning rather than silently returning nothing.

#### Does every match have statistics?

No. Most ATP, WTA and higher-tier Challenger matches do; some qualifying and lower-tier ITF matches do not have statistics published on Flashscore at all. Check `hasStatistics` on each row.

#### What does the statistics charge actually pay for?

Two extra requests per match (the statistics feed and the set-duration/tiebreak feed), which is where the aces, serve speed, tiebreak points, duration, referee and venue come from. It only fires when that data was actually returned - not on a request that came back empty.

#### Can I track a live match as it happens?

Yes - set `status` to "live" and run it every few minutes. Each row carries the current set and game score. This is not a websocket feed; each run is a fresh snapshot.

#### Which tours are covered?

Whatever Flashscore itself carries under tennis on a given day - ATP, WTA, Challenger, ITF and Davis Cup / Billie Jean King Cup ties have all been seen in the feed. Use the `tour` filter to narrow to one.

### 🛠️ Support

Open an issue on the Actor's Issues tab with the run URL, the input you used and what you expected. Include the run URL - it is what makes a report actionable.

### ⭐ Rate this Actor

If this saved you time building your own tennis data pipeline, please leave a review on the Reviews tab. Review count is the main trust signal a new Actor has, and reviews are what decide which fields and sources get built next. If something is broken or missing, please open an issue first - it is usually fixed within a day, and that is faster than a low rating gets it fixed.

### Changelog

#### 0.1 - 2026-09-12

- First release. Match results for ATP, WTA, Challenger and ITF, full match statistics, set-by-set duration and tiebreak points, referee and venue, optional head-to-head history, and live scores.

### Is it legal to scrape tennis match data?

This Actor reads only Flashscore's own publicly served match-data feed - the same data any visitor's browser loads when they open a match page, with no login and no paywall. Scraping public data is generally lawful, but you are responsible for how you use it: follow Flashscore's terms, applicable law and any data-protection rules that apply to you, and do not republish this data in a way that competes with Flashscore's own service.

# Actor input Schema

## `days` (type: `array`):

Day offsets from today, 0 = today, negative = past, positive = upcoming. Flashscore only serves this window 7 days back to 7 days ahead - any offset outside that range is ignored. Leave the default \[0] for just today.

## `status` (type: `string`):

Keep only matches in this state. "Live" also carries the current game score and which set is in progress.

## `tour` (type: `string`):

Keep only matches whose competition category contains this text, matched case-insensitively - for example "ATP", "WTA", "Challenger" or "ITF". Leave empty for every tour. This is a text match, not a fixed list, because Flashscore's own category labels are not a small closed set.

## `tournamentName` (type: `string`):

Keep only matches whose tournament name contains this text, matched case-insensitively - for example "US Open" or "Wimbledon".

## `playerName` (type: `string`):

Keep only matches where either player's name contains this text, matched case-insensitively - for example "Djokovic".

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

Flashscore match IDs to keep, one per line - use this to track specific matches you already know about. Still applied as a filter on top of the days above; a match outside the requested day range will not be found even if its ID is listed here.

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

On: fetch aces, double faults, serve speed, winners, unforced errors and net-point stats, plus set-by-set duration, tiebreak points, referee and venue. This is what a match row costs an extra "statistics" charge for - see pricing below. Off: only the scoreline from the match list.

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

On: add each player's recent results against this opponent and by surface. This is a much larger fetch (roughly 150 KB per match) so it is off by default; when it returns data it is billed under the same statistics charge as includeStatistics, no separate fee.

## `maxResults` (type: `integer`):

Stop after this many rows. Set 0 for no limit. The default keeps an exploratory run small and cheap; there is no hard ceiling, so scanning a full week is simply a choice you make.

## Actor input object example

```json
{
  "days": [
    0
  ],
  "status": "all",
  "tour": "ATP",
  "tournamentName": "US Open",
  "playerName": "Djokovic",
  "matchIds": [
    "6k4MeS57"
  ],
  "includeStatistics": true,
  "includeHeadToHead": false,
  "maxResults": 20
}
```

# Actor output Schema

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

No description

## `statistics` (type: `string`):

No description

## `runSummary` (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 = {
    "days": [
        0
    ],
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("webdata_labs/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 = {
    "days": [0],
    "maxResults": 20,
}

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

```

## MCP server setup

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