# Flashscore Tennis Scraper - Live Scores, Odds, H2H & History (`crawlplant/flashscore-tennis`) Actor

ATP, WTA, Challenger and ITF tennis from Flashscore, live and back to 1990: scores, odds from ~20 bookmakers (fair probability, closing odds), H2H over every meeting, Elo per surface, form, player stats, draws with rounds, rankings of any week since 1973. Works as an MCP tool for AI agents.

- **URL**: https://apify.com/crawlplant/flashscore-tennis.md
- **Developed by:** [CrawlPlant](https://apify.com/crawlplant) (community)
- **Categories:** Sports, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 Tennis Scraper - Live Scores, Odds, H2H & History

*Independent tool, not affiliated with, endorsed by or connected to Flashscore, Livesport, the ATP, the WTA, the ITF or any
bookmaker. It reads the public match pages and feeds that flashscore.com shows to every visitor, and official ranking lists.*

Every **ATP, WTA, Challenger and ITF** match, live or back to 1990, **one row per match with everything joined**:
set-by-set score, live game score, **odds from about 20 bookmakers with a link to each** (best price, opening and closing
odds, win probability without the bookmaker margin), **head-to-head over every meeting**, **Elo ratings and the Elo win
probability**, each player's **last 10 results**, **fatigue**, **ATP / WTA rank**, the **round**, **serve and return
statistics** and, on request, **point-by-point**. Separate modes give **live scores now**, **a tournament's whole draw**,
**a player's whole career**, **player profiles and statistics** (per season, surface or opponent's rank), **head-to-head
of any two players**, **Elo lists** and **ATP / WTA rankings of any week** (ATP back to 1973, WTA back to 2001).

### Why this one

- **See it live.** The Elo leaders of both tours, today's draw with each favourite by Elo and a career head-to-head, from
  the same database your runs read: [crawlplant.com/flashscore-tennis](https://crawlplant.com/flashscore-tennis/).
- **One row per match, ready for a model.** Result, odds, Elo, form, head-to-head, fatigue and statistics arrive in the
  same record, so there is nothing to join.
- **History, not just today.** More than 900,000 matches from 1990 (ATP and WTA from 1990, Challenger from 2008, ITF from
  2011\), updated every hour. Ask for any past day, a player's whole career or any edition of a tournament with its rounds.
- **Elo for every player and surface.** Ratings for 13,000+ players, overall and on hard, clay and grass, computed from
  every match since 1990. Each match row carries both players' Elo **before** the match and the Elo win probability, so
  you can compare it with the market's.
- **Odds you can use, not just numbers.** For every match: the best price for each player and the bookmaker offering it,
  the average price, the opening price, the move since opening, the average bookmaker margin, and the **fair win
  probability** (margin removed). Before a match, the margin of the best prices combined shows when they form a sure bet
  (below 0). Finished matches keep their **closing odds**, the benchmark for testing a betting model.
- **Bookmakers of your country.** Odds come from the bookmakers licensed where you are: about 20 in the UK (bet365,
  William Hill, Betfair, Sky Bet, Paddy Power, Ladbrokes, Betway, Unibet...), 7 in Germany, 6 in Poland, 3 in the US. Pick
  the country with one field. Each bookmaker row carries a link that opens the bookmaker.
- **Form as of the match, not as of today.** Form, fatigue, head-to-head and Elo are taken from the matches **before**
  the match start, so a finished match never counts in its own numbers and historical rows stay honest for back-testing.
- **Player statistics in one call.** Win-loss, titles, finals, tiebreaks, deciding sets, aces, 1st and 2nd serve points
  won, break points saved and converted, service and return games won, per season, surface, tour, or against top-10,
  top-50 and lower-ranked opponents.
- **Every tour, every day.** On 2026-09-29 the default run returned 100 of the day's singles matches (ATP, WTA,
  Challenger) in 12 seconds: 95 with odds from up to 20 UK bookmakers, all 87 played matches with serve and return
  statistics, 95 with form and head-to-head (the other 5 were cancelled), all 100 with rankings.

### What can you use it for?

- **Betting models and value betting**: Elo, form, surface record, fatigue, rank and fair probability side by side;
  closing odds and results of past seasons to measure your edge.
- **Odds comparison**: the best price per player across the bookmakers of a country, with links, for a comparison page or
  a Telegram / Discord bot.
- **Live score widgets and alerts**: current set and game score, who serves, in-play odds.
- **Sports journalism and previews**: career head-to-head, recent meetings, last 10 results, record against the top 10,
  rest days, ranking history.
- **Research datasets**: 35 years of results, rankings of every week, serve and return statistics, point-by-point.
- **AI agents**: "who is the favourite in Khachanov v Auger-Aliassime, what do Elo and the bookmakers say, and how rested
  are they?" answered from one record.

### Quick start

1. Click **Try for free** (or **Start**) with the default input: today's singles matches of every tour, ATP and WTA first,
   up to 100, with odds, head-to-head, form, Elo, statistics and rankings. About a minute; **about $0.40 on the Free
   plan**.
2. Pick the day (`dateFrom`: `today`, `tomorrow`, `yesterday`, `2019-07-14`, `-3`), the tours and the bookmakers' country,
   or another mode from the examples below.
3. Download the table as CSV, Excel or JSON, or save the input as a **task** and schedule it.

#### Copy to your AI assistant

Paste this into ChatGPT, Claude or any agent so it knows how to use the Actor:

```
crawlplant/flashscore-tennis on Apify: tennis from Flashscore (ATP, WTA, Challenger, ITF; singles and doubles), live and
back to 1990. Input: mode:
- "matches" (default): matches of days dateFrom..dateTo (UTC; "today"/"tomorrow"/"yesterday"/"2019-07-14"/"-3"; any
  past day, up to a week ahead);
- "live": in play now; "matchIds": ids or match URLs;
- "players": playerUrls (player names like "Jannik Sinner", or https://www.flashscore.com/player/<name>/<id>/) -> the
  whole career, newest first;
- "tournaments": tournamentUrls (https://www.flashscore.com/tennis/atp-singles/wimbledon/) + years -> every match with
  its round;
- "playerProfiles": playerUrls -> full name, birth date, country, photo, rank, Elo per surface;
- "playerStats": playerUrls + statsSplitBy (season | surface | tour | opponentRank | none) -> record and serve/return
  statistics;
- "h2h": playerUrls in pairs ("Sinner", "Alcaraz") -> head-to-head with every meeting;
- "elo": eloTour (atp | wta) + eloSurface (all | hard | clay | grass | carpet) -> Elo list;
- "rankings": rankingLists atp|wta|atp-race|wta-race|atp-doubles|wta-doubles, rankingDate (YYYY-MM-DD; ATP from 1973,
  WTA from 2001) or the latest.
Filters: tours (atp, wta, challenger-men, challenger-women, itf-men, itf-women, teams, other; [] = all), matchType
(singles default | doubles | all), status (all | scheduled | live | finished), tournaments / players (name contains),
surfaces (hard, clay, grass, carpet). Data: includeOdds (true), oddsCountry (GB default; DE, PL, US, AU...), oddsMarkets
(match-winner | all), includeH2H (true), includeStatistics (true), includePointByPoint (false), includeRankings (true),
sortBy (tour | time), maxItems (100).
Match record: matchId, url, date, startTime, tour, category, matchType, tournament, tournamentStage, round ("Final",
"Semi-finals", "1/8-finals", "Q1"...), tournamentCountry, surface, indoor, status (scheduled, live, finished, retired,
walkover, cancelled, postponed...), statusText, isLive, player1Name, player2Name, player1Rank, player2Rank, player1Form,
player2Form (last 10, newest first, "WLWWL..."), player1Elo, player2Elo, player1EloWinProbability, elo {player1, player2,
player1Surface, player2Surface, basis}, player1/player2 {name, playerId, url, country, players[], rank, rankingPoints},
winner ("player1"|"player2"), winnerName, setsPlayer1, setsPlayer2, score ("7-6(5) 3-6 6-2"), sets[], currentGame
{player1, player2, serving}, note, broadcasts, odds {bookmakerCount, player1Best, player1BestBookmaker, player2Best,
player2BestBookmaker, player1Average, player2Average, player1OpeningAverage, player2OpeningAverage, player1Probability,
player2Probability (margin removed), favourite, averageMarginPct, bestPricesMarginPct, player1MovePct, bookmakers[],
oddsUrl}, oddsMarkets[], headToHead {matches, player1Wins, player2Wins, surfaceMatches, surfacePlayer1Wins,
surfacePlayer2Wins, lastMeetingAt, lastWinner, recent[], source}, form {player1, player2: {last10, winPct,
surfaceWinPct, daysSinceLastMatch, matchesLast7Days, setsLast7Days, matchesLast14Days, setsLast14Days,
retiredLastMatch}}, statistics {match, sets[]}, pointByPoint[], durationMinutes.
Other records: ranking {list, rankingDate, rank, previousRank, rankChange, name, playerId, country, points,
officialId, birthDate}; player {playerId, name, fullName, country, birthDate, heightCm, plays, backhand, photoUrl, rank, elo, eloHard, eloClay,
eloGrass, eloPeak}; playerStats {playerName, splitBy, split, matches, wins, winPct, titles, acesPerMatch,
firstServeInPct, breakPointsSavedPct, returnGamesWonPct...}; h2h {player1Name, player2Name, matches, player1Wins,
player2Wins, surfaces[], meetings[]}; elo {eloRank, name, country, surface, rating, peak, matches}.
```

### Modes

| Mode | What you get | Typical run |
|---|---|---|
| `matches` (default) | Every match of the days you pick, any past day or a week ahead: results, live and scheduled, with odds, H2H, Elo, form, stats | 100 matches in 6-12 s; 1,671 matches over 9 days in 5.5 min |
| `live` | Matches in play right now: set and game score, server, in-play odds | seconds |
| `matchIds` | Specific matches by id or Flashscore URL, any date | seconds |
| `players` | A player's whole career (singles, doubles on request) and scheduled matches, newest first | Djokovic's 1,492 finished matches in 73 s |
| `tournaments` | Every match of a tournament edition with its round (qualifying Q1-Q3 included), any year | Wimbledon 2019: 239 matches in 11 s |
| `playerProfiles` | One row per player: full name, birth date, height, playing hand and backhand, country, photo, rank, Elo overall and per surface | seconds |
| `playerStats` | A player's win-loss, titles, tiebreaks, serve and return statistics per season, surface, tour or opponent's rank | seconds |
| `h2h` | Two players' head-to-head over their whole careers, per surface, with every meeting (exhibitions left out, like the official ATP/WTA count) | seconds |
| `elo` | The Elo list of the men's or women's tour, overall or on one surface | seconds |
| `sackmann` | Whole seasons of played singles in the columns of Jeff Sackmann's `atp_matches_YYYY.csv` / `wta_matches_YYYY.csv`: winner and loser, rank at the time, age, score, round, minutes, serve statistics | ATP 2025: 4,285 matches |
| `rankings` | ATP / WTA singles, doubles and race rankings, one row per player; `rankingDate` for any past week | all 6 current lists, 11,056 rows, in 15 s |

### Ready-to-use examples

Paste one into the **JSON** tab of the input. Costs are for the Free plan.

**1. Today's matches with odds, head-to-head, Elo and form (the default, ~$0.40)**

```json
{}
```

**2. Tomorrow's ATP and WTA singles with UK bookmaker odds**

```json
{ "dateFrom": "tomorrow", "tours": ["atp", "wta"] }
```

**3. Live tennis scores right now**

```json
{ "mode": "live", "includeH2H": false }
```

**4. Live ATP and WTA matches with point-by-point**

```json
{ "mode": "live", "tours": ["atp", "wta"], "includePointByPoint": true }
```

**5. Closing odds of yesterday's ATP and WTA matches, every market**

```json
{ "dateFrom": "yesterday", "tours": ["atp", "wta"], "status": "finished", "oddsMarkets": "all" }
```

**6. Odds from German bookmakers**

```json
{ "tours": ["atp", "wta"], "oddsCountry": "DE" }
```

**7. A past fortnight of ATP and WTA results with statistics (no odds, cheapest)**

```json
{ "dateFrom": "2024-06-01", "dateTo": "2024-06-14", "tours": ["atp", "wta"], "status": "finished", "includeOdds": false, "includeH2H": false, "maxItems": 2000 }
```

**8. One tournament, the last three days and tomorrow**

```json
{ "dateFrom": "-3", "dateTo": "+1", "tournaments": ["Beijing"] }
```

**9. Wimbledon 2019, every singles match with its round**

```json
{ "mode": "tournaments", "tournamentUrls": ["https://www.flashscore.com/tennis/atp-singles/wimbledon/"], "years": [2019], "includeOdds": false, "includeH2H": false, "maxItems": 300 }
```

**10. A player's whole career, finished singles matches**

```json
{ "mode": "players", "playerUrls": ["Novak Djokovic"], "status": "finished", "includeOdds": false, "includeH2H": false, "maxItems": 2000 }
```

**11. Player profiles with Elo per surface**

```json
{ "mode": "playerProfiles", "playerUrls": ["Jannik Sinner", "Novak Djokovic", "Iga Swiatek"] }
```

**12. A player's serve and return statistics per season**

```json
{ "mode": "playerStats", "playerUrls": ["Jannik Sinner"], "statsSplitBy": "season" }
```

**13. Record against top-10, top-50 and lower-ranked opponents**

```json
{ "mode": "playerStats", "playerUrls": ["https://www.flashscore.com/player/sinner-jannik/6HdC3z4H/"], "statsSplitBy": "opponentRank" }
```

**14. Head-to-heads of two pairs of players, every meeting**

```json
{ "mode": "h2h", "playerUrls": ["Sinner", "Alcaraz", "Sabalenka", "Swiatek"] }
```

**15. Clay-court Elo ratings, top 100 men**

```json
{ "mode": "elo", "eloTour": "atp", "eloSurface": "clay" }
```

**16. The ATP ranking of 1 January 2000**

```json
{ "mode": "rankings", "rankingLists": ["atp"], "rankingDate": "2000-01-01", "maxItems": 500 }
```

**17. ATP and WTA rankings today, top 200 each**

```json
{ "mode": "rankings", "rankingLists": ["atp", "wta"], "maxItems": 400 }
```

**18. The race rankings (season points)**

```json
{ "mode": "rankings", "rankingLists": ["atp-race", "wta-race"], "maxItems": 200 }
```

**19. Specific matches by URL, with point-by-point**

```json
{ "mode": "matchIds", "matchIds": ["https://www.flashscore.com/match/llKI871b/"], "includePointByPoint": true }
```

**20. Clay-court ITF women's matches today**

```json
{ "tours": ["itf-women"], "surfaces": ["clay"], "includeOdds": false }
```

**21. ATP and WTA doubles**

```json
{ "matchType": "doubles", "tours": ["atp", "wta"] }
```

**22. Scheduled matches only, sorted by start time**

```json
{ "dateFrom": "today", "dateTo": "tomorrow", "status": "scheduled", "sortBy": "time", "maxItems": 300 }
```

**23. The 2025 ATP season as a tennis\_atp-style CSV (export the dataset as CSV)**

```json
{ "mode": "sackmann", "years": [2025], "tours": ["atp"], "maxItems": 5000 }
```

### How to…

#### Find a specific tennis match

No id needed: give the day and a player's surname (or the tournament) and the row comes back with everything joined.
`{ "dateFrom": "2024-07-14", "players": ["Djokovic"] }` returns that day's Wimbledon final; `dateFrom` and `dateTo`
can span weeks or years (`"players": ["Sinner"]` over 2025 gives each of his matches). Also: a Flashscore match URL
in `matchIds`, all meetings of two players in `h2h` mode, or one player's matches in a tournament with `tournaments`
mode and `players`.

#### Get today's tennis results with odds

Run the default input. Each finished match has its score, winner, closing odds per bookmaker and the fair probability
the market gave each player. Filter with `tours` and `status: "finished"`.

#### Scrape Flashscore tennis live scores

`mode: "live"` returns every match in play: `score` (sets), `currentGame` (points and who serves), `statusText`
("Set 2", "Set 3 - Tiebreak"), in-play odds and point-by-point with `includePointByPoint`. Schedule it every few minutes
for a live board.

#### Download historical tennis results

Set `dateFrom` and `dateTo` to any past days (example 7), or ask for one player's career (example 10) or a tournament's
editions (example 9). Every match since 1990 has its score, winner, surface and both players' Elo before the match;
serve and return statistics come with ATP and WTA matches from 2012 (almost every one from 2020).

#### Replace Jeff Sackmann's tennis\_atp and tennis\_wta CSV files

`mode: "sackmann"` returns every played singles match of the seasons in `years` in the columns of his
`atp_matches_YYYY.csv` / `wta_matches_YYYY.csv`, in his order, so code that reads those files by column name keeps
working: `tourney_id`, `tourney_name`, `surface`, `draw_size`, `tourney_level`, `tourney_date`, `winner_seed` and
`winner_entry` (Q, WC, LL, PR, from the official ATP and WTA tour draws), `winner_name`, `winner_hand` and `winner_ht`
(from the ATP and WTA player profiles), `winner_ioc`, `winner_age`, `loser_*`, `score` ("7-6(5) 6-4", "RET", "W/O"),
`best_of`, `round` (R128 ... F, Q1-Q3),
`minutes`, the serve columns `w_ace` ... `l_bpFaced` and `winner_rank` / `loser_rank` with points from the ranking of
that week (official ATP and WTA lists). Export the dataset as CSV (example 23). Use `tours: ["challenger-men"]` or
`["itf-men"]` for his qual\_chall and futures files. Four extra columns close each row: `match_id`, `tour`,
`winner_elo` and `loser_elo` (pre-match Elo).
How it differs from his files: player ids are Flashscore ids (strings); qualifying rounds come in the same file (keep `round` values that don't start
with "Q" for his main-draw file); `draw_size` is counted from the main-draw matches played; `tourney_level` uses G, M,
F, D, A for tour level (WTA 1000 included in A), C for Challenger, S for ITF. A full ATP season (4,285 matches with
qualifying, 2025) costs about $4.30 on the Free plan.

#### Get tennis Elo ratings

`mode: "elo"` lists the men's or women's tour by Elo, overall or on one surface, with each player's peak and number of
rated matches. Match rows carry `player1Elo`, `player2Elo` and `player1EloWinProbability` (overall and surface Elo
blended), rated before the match.

#### Tennis head-to-head of two players

`mode: "h2h"` with two player URLs gives every meeting of their careers, the wins of each, the split by surface and each
meeting's round, score and Elo. In match rows, `headToHead` counts every meeting before that match.

#### Player statistics against top-10 opponents

`mode: "playerStats"` with `statsSplitBy: "opponentRank"` splits a career by the opponent's rank at the time (top 10,
11-50, 51-100, 101-250, 251+): matches, wins, win %, tiebreaks and deciding sets won, serve and return percentages.
`season`, `surface` and `tour` split it the other ways.

#### Tournament draw results with rounds

`mode: "tournaments"` with a tournament URL and `years` returns every match of those editions with its round, from the
first qualifying round (`Q1`) to the final. The latest edition includes its scheduled matches.

#### Historical ATP and WTA rankings

`mode: "rankings"` with `rankingDate` returns the official list of that week: rank, points, tournaments played, the
player's birth date and Flashscore id. ATP lists go back to 1973, WTA lists to 2001.

#### Get tennis odds from UK bookmakers (or any country)

Set `oddsCountry` (GB, DE, PL, IT, ES, FR, US, AU, NG...). `odds.bookmakers` lists every bookmaker's price with its link;
`odds.player1Best` / `player2Best` give the best price and who offers it. `oddsMarkets: "all"` adds set winner, total
games and sets, handicaps and correct score.

#### Find value bets: fair probability and bookmaker margin

`odds.player1Probability` is the market's win probability with the margin removed (the average of each bookmaker's
normalised prices); `player1EloWinProbability` is Elo's. Compare them with your model; `odds.player1Best` is the price
to take. Before the match, `odds.bestPricesMarginPct` below 0 means the best prices of two bookmakers form a sure bet.

#### Download tennis match statistics and point-by-point

`statistics.match` and `statistics.sets[]` carry aces, double faults, 1st serve %, points won on 1st and 2nd serve,
break points saved and converted, service and return games won, with the counts (`player1Won`, `player1Total`).
`includePointByPoint: true` adds every game: server, winner, whether serve was broken, the points in order and how many
break, set and match points it had.

### Input options

| Field | Default | What it does |
|---|---|---|
| `mode` | `matches` | `matches`, `live`, `matchIds`, `players`, `tournaments`, `playerProfiles`, `playerStats`, `h2h`, `elo`, `rankings` |
| `dateFrom` / `dateTo` | `today` / same day | Days in UTC: `today`, `yesterday`, `tomorrow`, `2019-07-14`, `-3`, `+1` |
| `matchIds` | `[]` | Match ids or URLs (matchIds mode) |
| `playerUrls` | `[]` | Player names ("Jannik Sinner", "Swiatek") or page URLs (players, playerProfiles, playerStats, h2h; h2h takes them in pairs) |
| `tournamentUrls` / `years` | `[]` / latest | Tournament URLs and editions (tournaments mode) |
| `statsSplitBy` | `season` | `season`, `surface`, `tour`, `opponentRank`, `none` (playerStats mode) |
| `eloTour` / `eloSurface` | `atp` / `all` | Elo list (elo mode) |
| `rankingLists` / `rankingDate` | `["atp","wta"]` / latest | Lists and week for rankings mode |
| `tours` | all | `atp`, `wta`, `challenger-men`, `challenger-women`, `itf-men`, `itf-women`, `teams`, `other` |
| `matchType` | `singles` | `singles`, `doubles`, `all` |
| `status` | `all` | `scheduled`, `live`, `finished` |
| `tournaments`, `players` | `[]` | Name contains (case-insensitive) |
| `surfaces` | all | `hard`, `clay`, `grass`, `carpet` |
| `includeOdds` / `oddsCountry` / `oddsMarkets` | `true` / `GB` / `match-winner` | Bookmaker odds |
| `includeH2H` | `true` | Head-to-head, form, fatigue |
| `includeStatistics` | `true` | Serve and return statistics |
| `includePointByPoint` | `false` | Every game and point |
| `includeRankings` | `true` | Rank and points of each player (singles) |
| `sortBy` | `tour` | `tour` (ATP, WTA, Challenger, ITF, then time) or `time` |
| `maxItems` | `100` | Maximum records, counted after the filters |

### Example output

One match (shortened: 2 of 19 bookmakers, 2 of 17 statistics):

```
{
  "recordType": "match",
  "matchId": "WdDDlcIl",
  "url": "https://www.flashscore.com/match/WdDDlcIl/",
  "date": "2026-09-29",
  "startTime": "2026-09-29T02:10:00.000Z",
  "tour": "atp",
  "category": "ATP - Singles",
  "tournament": "Tokyo",
  "tournamentStage": "Qualification",
  "round": "Q2",
  "surface": "hard",
  "status": "finished",
  "player1Name": "Tsitsipas S.",
  "player2Name": "Hijikata R.",
  "player1Rank": 44,
  "player2Rank": 82,
  "player1Form": "WWWLWWWLWL",
  "player2Form": "WLWLLWWLWW",
  "player1Elo": 2088,
  "player2Elo": 1976,
  "player1EloWinProbability": 0.6427,
  "winner": "player1",
  "winnerName": "Tsitsipas S.",
  "score": "6-4 6-3",
  "durationMinutes": 67,
  "odds": {
    "country": "GB",
    "bookmakerCount": 19,
    "player1Best": 1.3, "player1BestBookmaker": "7BetUK",
    "player2Best": 3.75, "player2BestBookmaker": "Skybet",
    "player1Average": 1.28, "player2Average": 3.47,
    "player1OpeningAverage": 1.34, "player2OpeningAverage": 3.06,
    "player1Probability": 0.7298, "player2Probability": 0.2702,
    "favourite": "player1",
    "averageMarginPct": 6.77,
    "bestPricesMarginPct": null,
    "player1MovePct": -4.2,
    "bookmakers": [
      { "bookmaker": "10bet", "player1": 1.29, "player2": 3.5, "player1Opening": 1.38, "player2Opening": 2.9,
        "url": "https://www.flashscore.com/bookmaker/14/?from=odds-comparison&sport=2" },
      { "bookmaker": "7BetUK", "player1": 1.3, "player2": 3.4, "player1Opening": 1.3, "player2Opening": 3.1,
        "url": "https://www.flashscore.com/bookmaker/895/?from=odds-comparison&sport=2" }
    ],
    "oddsUrl": "https://www.flashscore.com/match/WdDDlcIl/#/odds-comparison/home-away/full-time"
  },
  "headToHead": { "matches": 2, "player1Wins": 2, "player2Wins": 0, "surfaceMatches": 2, "lastMeetingAt": "2023-10-07T04:40:00.000Z", "source": "database (every meeting)" },
  "elo": { "player1": 2088, "player2": 1976, "player1Surface": 2003, "player2Surface": 1911, "basis": "pre-match" },
  "form": {
    "player1": { "last10": "WWWLWWWLWL", "winPct": 61.2, "surfaceWinPct": 55.1, "daysSinceLastMatch": 1,
                 "matchesLast7Days": 1, "setsLast7Days": 2, "matchesLast14Days": 3, "setsLast14Days": 7, "retiredLastMatch": false },
    "player2": { "last10": "WLWLLWWLWW", "winPct": 55.1, "surfaceWinPct": 60.4, "daysSinceLastMatch": 1,
                 "matchesLast7Days": 3, "setsLast7Days": 6, "matchesLast14Days": 3, "setsLast14Days": 6, "retiredLastMatch": false }
  },
  "statistics": {
    "match": {
      "aces": { "player1": 6, "player2": 4 },
      "firstServePointsWon": { "player1": 81, "player2": 69, "player1Won": 26, "player1Total": 32, "player2Won": 25, "player2Total": 36 }
    }
  },
  "scrapedAt": "2026-09-29T20:31:06.488Z",
  "source": "live"
}
```

### Output fields

| Field | Meaning |
|---|---|
| `matchId`, `url` | Flashscore's match id and page |
| `date`, `startTime` | Day (UTC) and start time (ISO) |
| `tour`, `category`, `matchType` | `atp`, `wta`, `challenger-men`...; "ATP - Singles"; singles / doubles / team |
| `tournament`, `tournamentStage`, `round` | "Beijing", "Qualification" (null = main draw), "Final" / "Semi-finals" / "1/8-finals" / "Q1" |
| `surface`, `indoor`, `tournamentCountry` | hard / clay / grass / carpet, indoor court, host country |
| `status`, `statusText`, `isLive` | scheduled, live, finished, retired, walkover, cancelled, postponed, interrupted...; "Set 2" |
| `player1Name`, `player2Name`, `player1Rank`, `player2Rank`, `player1Form`, `player2Form` | Flat fields for tables |
| `player1Elo`, `player2Elo`, `player1EloWinProbability`, `elo` | Elo of both players before the match, overall and on the surface; the win probability from both |
| `player1`, `player2` | name, playerId, url, country, `players[]` (both players of a doubles pair), rank, rankingPoints, rankingDate |
| `winner`, `winnerName`, `setsPlayer1`, `setsPlayer2`, `score`, `sets[]` | Result; tiebreak points per set in `sets[]` |
| `currentGame` | In play: points of each player and who serves |
| `note` | Flashscore's note ("Interrupted due to rain.", "X - withdrawn.") |
| `broadcasts` | TV channels and bookmaker live streams, with links |
| `odds` | Match-winner odds per bookmaker and the consensus fields (see [value bets](#find-value-bets-fair-probability-and-bookmaker-margin)) |
| `oddsMarkets[]` | With `oddsMarkets: "all"`: bookmaker, market (HOME\_AWAY, OVER\_UNDER, ASIAN\_HANDICAP, CORRECT\_SCORE, ODD\_OR\_EVEN), scope (FULL\_TIME, FIRST\_SET...), selection, line, lineType, odds, opening |
| `headToHead` | Meetings before this match, on all surfaces and this surface, the last 5; `source` says whether it counts every meeting |
| `form.player1`, `form.player2` | Last 10, win % (up to 50 matches), surface form and win %, rest days, matches and sets in 7 / 14 days, retired last match |
| `statistics` | Match and per-set serve / return statistics |
| `pointByPoint[]` | Games with server, winner, break, points, break / set / match points |
| `endTime`, `durationMinutes` | When a finished match ended and its length in minutes |
| `scrapedAt`, `source`, `store` | When it was read; `live` (read now) or `cache` (from the tennis database); `flashscore` |

The other modes return their own record types (`recordType`): `ranking`, `player`, `playerStats`, `h2h` and `elo`, with
the fields listed in [Copy to your AI assistant](#copy-to-your-ai-assistant); each has its own table view in the dataset.

"player1" is the player Flashscore lists first (its "home" side); tennis has no home side, so it carries no advantage.

### How is Elo calculated?

The classic Elo model used for tennis: every player starts at 1500, each match moves both ratings by K × (result −
expected result), and K shrinks as a player plays more matches (K = 250 / (matches + 5)^0.4), so new players settle
quickly and established ones move slowly. There is one rating over all matches and one per surface; the win probability
blends the two equally. Walkovers don't count; retirements count as played. Ratings are recomputed every night over
every match since 1990, and a match row keeps the ratings both players had before it.

### Alerts and scheduling

- **Morning preview**: schedule example 2 daily at 06:00 UTC and send the dataset to Google Sheets, Slack or e-mail with an
  Apify integration.
- **Live board**: schedule example 3 every 5 minutes; each run is a fresh snapshot of every match in play.
- **Weekly back-test data**: example 5 on Mondays with `dateFrom: "-7"` and `dateTo: "yesterday"`.
- **Weekly rankings**: example 17 every Monday afternoon (the lists are published on Mondays).
- **Webhooks**: add a webhook on "run succeeded" to push the dataset into your own system.

### Run it through the API

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('crawlplant/flashscore-tennis').call({ dateFrom: 'tomorrow', tours: ['atp', 'wta'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const m of items) console.log(m.player1Name, m.player1Elo, m.odds?.player1Best, 'v', m.player2Name, m.player2Elo, m.odds?.player2Best);
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("crawlplant/flashscore-tennis").call(run_input={"mode": "live"})
for m in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(m["player1Name"], m["score"], m["currentGame"], m["player2Name"])
```

### Use with AI agents

Add the Actor as a tool through Apify's MCP server: `https://mcp.apify.com?tools=crawlplant/flashscore-tennis`. An agent
can ask for one match (a day plus a surname, or `matchIds`), a player's profile, statistics or career by name, a
head-to-head or an Elo list, and
read the answer from flat fields with plain values (numbers, ISO dates), easy to quote in a preview or store for
retrieval.

### How much does it cost to scrape Flashscore tennis?

Pay per result: platform usage is included.

| Event | Free plan | Starter | Scale | Business and up |
|---|---|---|---|---|
| Match or ranking row (per 1,000) | $1.00 | $0.90 | $0.80 | $0.70 |
| Bookmaker odds, per match that has odds (per 1,000, on top of the row) | $1.50 | $1.35 | $1.20 | $1.05 |
| Head-to-head, form and fatigue, per match (per 1,000, on top of the row) | $1.50 | $1.35 | $1.20 | $1.05 |
| Point-by-point, per match (per 1,000, on top of the row) | $1.00 | $0.90 | $0.80 | $0.70 |
| Elo list row (per 1,000) | $1.00 | $0.90 | $0.80 | $0.70 |
| Player profile (per 1,000) | $3.00 | $2.70 | $2.40 | $2.10 |
| Player statistics row: one season, surface, tour or rank band (per 1,000) | $5.00 | $4.50 | $4.00 | $3.50 |
| Head-to-head of two players, with every meeting (per 1,000) | $5.00 | $4.50 | $4.00 | $3.50 |
| Actor start (per run) | $0.00005 | $0.00005 | $0.00005 | $0.00005 |

Statistics, Elo, rounds, rankings, live scores and TV / stream links are included in the row price. Turn off what you
don't need (`includeOdds`, `includeH2H`) and those events are not charged.

| Example on the Free plan | Rows | Cost |
|---|---|---|
| Default run: 100 matches with odds, head-to-head and form | 100 | ~$0.40 |
| 1,000 finished matches with statistics only | 1,000 | ~$1.00 |
| 50 live matches with odds, form and point-by-point | 50 | ~$0.25 |
| ATP and WTA rankings, top 200 each | 400 | ~$0.40 |
| A player's career, 1,492 finished matches with statistics | 1,492 | ~$1.49 |
| A player's statistics per season (20 seasons) | 20 | ~$0.10 |
| Head-to-head of 10 pairs of players | 10 | ~$0.05 |

Set a **maximum cost per run** in the run options to stop a large run at your budget.

Other tennis Actors on the Apify Store, Free-plan prices read from the Store on 2026-09-29:

| Actor | Users (30 days) | Price |
|---|---|---|
| extractify-labs/flashscore-tennis-matches | 875 | $1.00 / 1,000 matches + $0.00005 per run |
| crawlstone/tennis-scraper (SofaScore, Tennis Abstract) | 1,059 | $8.00 / 1,000 results |
| sourabhbgp/flashscore-tennis-scraper | 25 | $1.00 / 1,000 results, one mode (results, point-by-point, statistics, H2H or odds) per run |
| zen-studio/flashscore-tennis-api | 1 | $1.99 / 1,000 matches + $4.99 / 1,000 odds requests + $5.99 / 1,000 detail requests |
| memo23/flashscore-sofascore-tennis-scraper | 19 | $5.00 / 1,000 items + $15.00 / 1,000 match details + $5.00 / 1,000 H2H + $0.005 per run |
| **This Actor** | new | $1.00 / 1,000 matches + $1.50 odds + $1.50 head-to-head and form |

### Reliability

- Each request that fails is retried and, when an address is refused, sent again through another route; a match whose odds
  or statistics still can't be read keeps every other field, with the missing part null.
- The run always finishes: it stops starting new requests shortly before the time limit and saves what it has.
- Every run writes a summary to the key-value store (`OUTPUT`): records saved, how many came from the tennis database and
  how many were read live, requests, warnings (filters that matched nothing, unknown ids) and the events charged.

### Data sources and freshness

- **Live and upcoming matches, odds, in-play scores**: read from Flashscore at the moment of the run.
- **The tennis database**: every match Flashscore lists from 1990, kept up to date every hour (results, rounds, statistics,
  point-by-point), with Elo recomputed every night.
- **Rankings**: the latest lists from Flashscore (singles, doubles, race), and the official weekly ATP (from 1973) and WTA
  (from 2001) singles lists for past weeks, updated every Monday.

### Troubleshooting

- **Fewer matches than expected**: `matchType` defaults to `singles` and `maxItems` to 100; filters combine (all must
  match). A day in UTC may start or end in the middle of your evening session.
- **No odds on a match**: a few ITF and interrupted matches are not priced (10 of 129 ITF matches on 2026-09-29); the
  field is null and not charged. A different `oddsCountry` may have more bookmakers.
- **Form is empty**: brand-new players or team ties (form and head-to-head are then null and not charged); doubles form
  is the pair's.
- **No Elo on a match**: doubles, team ties and players with no rated singles match yet (a first match on the tour).
- **Player not found, or the wrong one**: a name picks the best-known player whose names hold every word you wrote
  ("Alcaraz" is Carlos); the run's warnings name the others it matched. For one of them, paste their Flashscore page URL
  (`/player/sinner-jannik/6HdC3z4H/`).

### FAQ

#### How much does it cost to scrape Flashscore tennis?

About $0.40 for the default run of 100 matches with odds and form; $1.00 per 1,000 matches for results and statistics
alone. See [the price table](#how-much-does-it-cost-to-scrape-flashscore-tennis).

#### How far back does it go?

Matches: every match Flashscore lists from 1990 (ATP and WTA; Challenger from 2008, ITF from 2011). Serve and return
statistics: ATP and WTA matches from 2012, almost every one from 2020. Rankings: ATP from 1973, WTA from 2001.

#### Which bookmakers are included?

The bookmakers Flashscore shows in the country you pick. For GB on 2026-09-29: bet365, 10bet, 7BetUK, Betano, Betfair,
Betfred, BetMGM, BetUK, BetVictor, Betway, Coral, Ladbrokes, Midnite, Paddy Power, Parimatch, Sky Bet, SpreadEX,
Talksport Bet, Unibet, William Hill.

#### Are the odds live?

For a match in play the odds are the bookmakers' in-play prices at the moment of the run; before the match they are
pre-match prices; after it, the closing prices. Opening prices come with every row.

#### How is the win probability calculated?

From the odds: for each bookmaker, 1/odds of each player divided by the sum of both (this removes the margin); the
average over the bookmakers is `player1Probability`. From Elo: see [How is Elo calculated?](#how-is-elo-calculated).

#### Does it include doubles and team events?

Yes: `matchType: "doubles"` (both players of each pair) and `tours: ["teams"]` with `matchType: "all"` (Davis Cup, Billie
Jean King Cup, Laver Cup ties).

#### Is it legal to scrape Flashscore?

The Actor reads public pages without logging in, at a polite pace. Match results, rankings and odds are factual data;
how you use and publish them is your responsibility, so check the site's terms for your use case.

### Limits

- Up to 5 requests at a time to each Flashscore host; about 5 matches per second with odds, head-to-head and
  statistics (1,671 in 5.5 minutes on 2026-09-30). Rows from the tennis database without odds come much faster.
- Up to 100 player URLs per run in the player modes.
- Rankings are attached to singles players (ATP list for men's tours, WTA for women's); Elo to singles matches.

### Privacy

The data is about professional players' public sporting results and profiles (names, countries, birth dates, photos,
scores, rankings). No logins, no account data. Each run sends the developer anonymous feature-usage statistics (the mode
and which options were used, never the players or matches you asked for); your Apify account id is replaced by a one-way
hash on arrival.

# Actor input Schema

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

matches = every match of the days you pick (any past date; the day lists reach 7 days back, older days come from our tennis database), with results, odds, head-to-head, form, Elo and statistics; live = matches in play right now; matchIds = specific matches by id or URL (any date); players = a player's matches, newest first (the whole career from the database, else about 40 recent); rankings = ATP / WTA rankings, one row per player (the latest, or the list of a past date); tournaments = every match of a tournament edition with its round; playerProfiles = one row per player: age, country, photo, rank, Elo per surface; playerStats = a player's record and serve / return statistics per season, surface, tour or opponent's rank; h2h = two players' head-to-head with every meeting; elo = the Elo list of a tour and surface; sackmann = every played singles match of the seasons in "years" (or the dateFrom..dateTo range; neither = this season) in the columns of Jeff Sackmann's atp\_matches / wta\_matches CSV files: winner and loser, rank at the time, age, score, round, minutes, serve statistics (tours: ATP and WTA unless set).

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

Matches mode: the first day, in UTC. today, yesterday, tomorrow, a date like 2026-09-28, or an offset like -3 (three days ago) or +1. Flashscore's daily lists go 7 days back; days ahead list what is already scheduled (usually 1-2 days).

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

Matches mode: the last day, inclusive, same formats as From day. Empty = only the From day. Example: From -7 and To today = the last 8 days.

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

matchIds mode: Flashscore match ids (the 8 characters after mid= or /match/, e.g. llKI871b) or full match URLs. Any date, also older than 7 days. Adds the round (e.g. Final) and the match duration.

## `playerUrls` (type: `array`):

players, playerProfiles, playerStats and h2h modes: player names, e.g. "Jannik Sinner" or "Swiatek" (the best-known player of that name), or Flashscore player page URLs, e.g. https://www.flashscore.com/player/sinner-jannik/6HdC3z4H/, to pick one exactly. h2h mode takes them in pairs: 1st v 2nd, 3rd v 4th...

## `rankingLists` (type: `array`):

rankings mode: which lists. Each has about 2,000 players (top 100 in one request, the rest in a second one).

## `rankingDate` (type: `string`):

rankings mode: the list of this date (YYYY-MM-DD; ATP back to 1973, WTA back to 2001); empty = the latest list.

## `tournamentUrls` (type: `array`):

tournaments mode: Flashscore tournament URLs, e.g. https://www.flashscore.com/tennis/atp-singles/wimbledon/ (or an edition: .../wimbledon-2019/).

## `years` (type: `array`):

tournaments mode: the editions to read, e.g. \[2019, 2023]; empty = the latest edition. sackmann mode: the seasons, e.g. \[2024, 2025] (from 1990).

## `statsSplitBy` (type: `string`):

playerStats mode: one row per season, surface, tour, opponent's rank band (top 10, 11-50, 51-100, 101-250, 251+) or all matches together.

## `eloTour` (type: `string`):

elo mode: men (ATP, Challenger, ITF) or women (WTA, Challenger, ITF).

## `eloSurface` (type: `string`):

elo mode: overall Elo or Elo on one surface.

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

Keep only these tours. Empty = all. challenger-women is the WTA 125 level; teams = Davis Cup, Billie Jean King Cup, Laver Cup ties; other = exhibitions, juniors and the rest.

## `matchType` (type: `string`):

singles (default), doubles (includes mixed doubles), or all (also team ties).

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

all, scheduled (not started, delayed, postponed), live (in play), finished (including retired, walkover and awarded).

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

Keep matches whose tournament name or category contains any of these, case-insensitive, e.g. Beijing, US Open, W100, Challenger.

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

Keep matches with a player whose name contains any of these, case-insensitive (Flashscore writes names as "Sinner J."): Sinner, Swiatek. A player id also works.

## `surfaces` (type: `array`):

Keep matches on these surfaces. Empty = all. Matches without a surface (team ties) are dropped when this is set.

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

Match-winner odds of each bookmaker (current and opening; closing odds for finished matches), best and average price, win probability without the bookmaker margin, and a link to each bookmaker. Charged per match that has odds (see Pricing).

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

Bookmakers differ by country (licences). Two-letter code: GB (about 20 bookmakers: bet365, William Hill, Betfair, Sky Bet, Paddy Power...), DE, PL, IT, ES, FR, US, AU, NG and others.

## `oddsMarkets` (type: `string`):

match-winner = the summary and one row per bookmaker; all = also every other market in oddsMarkets: set winner, total games and sets, game and set handicaps, correct score (about 100-200 prices per match).

## `includeH2H` (type: `boolean`):

Head-to-head record (all surfaces and this surface, last 5 meetings), each player's last 10 results, win % overall and on this surface, days since the last match, matches and sets in the last 7 and 14 days, and whether they retired in their last match. Computed as of the match start. Charged per match (see Pricing).

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

Serve and return statistics for matches that started: aces, double faults, 1st serve %, points won on 1st / 2nd serve, break points saved and converted, service and return games won, for the match and each set. Included in the base price.

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

Every game of matches that started: server, winner, breaks, the points in order and break / set / match points. Charged per match (see Pricing).

## `includeRankings` (type: `boolean`):

Current ATP / WTA singles rank and points of each player (singles matches). Two small requests per list per run.

## `sortBy` (type: `string`):

tour = ATP first, then WTA, Challenger, ITF, each by start time; time = by start time only. Players mode is always newest first.

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

Maximum records to return (matches or ranking rows), counted after the filters. A busy day has 300-450 singles matches across all tours.

## Actor input object example

```json
{
  "mode": "matches",
  "dateFrom": "today",
  "matchIds": [],
  "playerUrls": [],
  "rankingLists": [
    "atp",
    "wta"
  ],
  "tournamentUrls": [],
  "years": [],
  "statsSplitBy": "season",
  "eloTour": "atp",
  "eloSurface": "all",
  "tours": [],
  "matchType": "singles",
  "status": "all",
  "tournaments": [],
  "players": [],
  "surfaces": [],
  "includeOdds": true,
  "oddsCountry": "GB",
  "oddsMarkets": "match-winner",
  "includeH2H": true,
  "includeStatistics": true,
  "includePointByPoint": false,
  "includeRankings": true,
  "sortBy": "tour",
  "maxItems": 100
}
```

# Actor output Schema

## `results` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlplant/flashscore-tennis").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("crawlplant/flashscore-tennis").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 '{}' |
apify call crawlplant/flashscore-tennis --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlplant/flashscore-tennis"
        }
    }
}
```

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/3Ltk7F4lhZy7M610H/builds/iORxx1fcvjUGgMAHh/openapi.json
