datagrit

datagrit › Data › Tennis Stats Scraper - ATP & WTA Scores, Rankings

Data

Tennis Stats Scraper - ATP & WTA Scores, Rankings

ATP and WTA tennis match results with set and tiebreak scores, seeds, rankings and player profiles for any date range.

Run it on Apify StoreUse the APIfrom $4.20 per 1,000 results + $10 per run · no code needed
from $4.20 per 1,000 results + $10 per runpay only for result rows you get
JSON · CSV · Excelexport or call via API
Scheduled runsdaily or weekly feeds with Apify schedules
v0.2updated 2026-09-30

Tennis Stats Scraper returns ATP and WTA tennis matches for any date range as clean, flat rows: tournament, round, players with country and seed, status, winner, the games of every set and the tiebreak points of every tiebreak. The same run can add the current ATP and WTA singles rankings with points and movement, and player profiles with height, playing hand and season record, titles and prize money. It reads the public ESPN tennis JSON API, needs no proxy and no browser, and exports to JSON, CSV or Excel, the Apify API, n8n, Make or AI agents through MCP.

Who is it for?

What data do you get?

Matches

Every match of the ATP and WTA tournaments ESPN covers, including the Grand Slams with mixed doubles, qualifying rounds and doubles. Doubles rows name both partners of each pair. The tour of a match follows its draw: the women's matches of a combined event such as the China Open are WTA, mixed doubles are Mixed. Junior draws and legends (over-35 and over-45) events are skipped and counted in the run status. A match that ESPN lists in both the ATP and the WTA feed is returned once; if the two feeds differ at that moment, the more advanced state (finished over in progress over scheduled) is kept and the run status says so.

Scores and data quality

Scores are given from the winner's point of view with tiebreak points in brackets and a deciding match tiebreak (doubles and mixed doubles) in square brackets such as [10-4]; ret. marks a retirement and w/o a walkover. ESPN writes match tiebreaks in two ways, either as a 1-0 set with tiebreak points or as a plain set such as 10-4; both come out as [10-4], with the points in the tiebreak fields of that set and 1-0 as its games. A plain 10-8 last set at a Grand Slam before 2023 stays as games, because it can be an advantage set. For finished matches, scoreConsistent is false when the published set scores do not add up to the recorded winner or ESPN's result note still says "leads" or "is tied with"; this happens for a few older matches that were never completed at the source. The run status counts how many completed matches carry set scores and how many players have a known country, and the run fails instead of returning empty scores if the source stops publishing them.

Rankings and players

Rankings are the current singles lists from ESPN's ranking endpoint, which holds the top 150 of the ATP and of the WTA (asking for more returns the same 150). The run status gives the share of ranked players with points and with a country code. Player profiles come from ESPN's player records with the season statistics of the player's tour.

Is it legal to scrape this data?

The Actor reads the public JSON endpoints that serve ESPN's own tennis scoreboard, ranking and player pages. It does not log in, use cookies or bypass any access control. Match results, rankings and player facts such as height or date of birth are published facts about professional athletes. How you use the data, for example in a commercial product, is your responsibility. This description is not legal advice.

Output fields

Every result is one flat record, so it drops straight into a spreadsheet, a database or a CRM.

FieldTypeDescriptionExample
recordTypestringWhat the row describes: match, ranking, player, or status (the single row written when a run returns nothing).match
idstringESPN identifier of the match. Stable across runs and identical in the ATP and WTA feeds.186257
tourstringATP for men's matches and men's rankings, WTA for women's, Mixed for mixed doubles. Derived from the draw of the match, so a women's match at a combined event is WTA.ATP
matchTypestringsingles or doubles (mixed doubles have matchType doubles and tour Mixed).singles
drawNamestringDraw name as ESPN labels it.Men's Singles
tournamentIdstringESPN tournament identifier, the same every year (959 is the China Open).959
tournamentEventIdstringESPN identifier of this edition of the tournament: tournament ID and season.959-2026
tournamentNamestringTournament name as ESPN shows it today; for older seasons ESPN uses the current sponsor name.China Open
seasonintegerSeason year of the tournament edition.2026
grandSlambooleanTrue for the Australian Open, Roland Garros, Wimbledon and the US Open.false
tournamentStartDatestringISO 8601 start of the tournament edition as ESPN lists it.2026-09-27T04:00:00.000Z
tournamentEndDatestringISO 8601 end of the tournament edition as ESPN lists it.2026-10-12T03:59:00.000Z
locationstringCity and country of the tournament.Beijing, China PR
courtstringCourt the match was played or is scheduled on, when ESPN publishes it.Court 7
roundstringRound of the draw, for example Qualifying 1st Round, Round 1, Quarterfinal, Final.Qualifying 1st Round
qualifyingbooleanTrue for qualifying rounds.true
startTimestringISO 8601 start time in UTC. The date range filter compares the UTC date of this time.2026-09-28T03:00:00.000Z
startTimeConfirmedbooleanFalse when ESPN only has a placeholder time for a scheduled match.true
statusstringscheduled, in_progress, finished, retired, walkover, postponed, cancelled or suspended.finished
statusDetailstringStatus text from ESPN, for example Final, Retired or 3rd Set.Final
completedbooleanTrue when the match is over (finished, retired or walkover).true
player1NamestringPlayer 1 as ordered by ESPN; for doubles both names joined with " / ". Null while the player is not yet known (TBD).Yannick Hanfmann
player1IdstringESPN ID of player 1; for doubles the ESPN pair ID (two player IDs joined with a hyphen). Use a singles ID in the players input to get the profile.3322
player1PlayerIdsstringESPN player IDs of player 1, comma-separated for doubles.3322
player1CountrystringCountry name of player 1 (both partners for doubles, joined with " / " when they differ).Germany
player1CountryCodestringThree-letter country code ESPN uses for player 1 (ESPN codes, for example SER for Serbia).GER
player1SeedintegerSeed of player 1 in this draw; null when unseeded.
player2NamestringPlayer 2 as ordered by ESPN; for doubles both names joined with " / ". Null while the player is not yet known (TBD).Tomas Machac
player2IdstringESPN ID of player 2; for doubles the ESPN pair ID.3811
player2PlayerIdsstringESPN player IDs of player 2, comma-separated for doubles.3811
player2CountrystringCountry name of player 2.Czechia
player2CountryCodestringThree-letter country code ESPN uses for player 2.CZE
player2SeedintegerSeed of player 2 in this draw; null when unseeded.
winnerinteger1 or 2: which player won. Null while the match is not decided.2
winnerNamestringName of the winner (pair for doubles).Tomas Machac
loserNamestringName of the loser (pair for doubles).Yannick Hanfmann
setsPlayedintegerNumber of sets with a score, including an unfinished set of a retired or live match.3
player1SetsWonintegerSets won by player 1. An unfinished set counts for nobody.1
player2SetsWonintegerSets won by player 2.2
scorestringScore from the winner's point of view (player 1 first while undecided), tiebreak points in brackets, a deciding match tiebreak in square brackets such as [10-4]; "ret." marks a retirement and "w/o" a walkover. In the set fields ESPN stores a match tiebreak as a 1-0 set with the tiebreak points.7-6(9-7) 6-7(6-8) 6-4
scoreConsistentbooleanFor finished matches: true when the recorded winner won more sets. False flags a match whose published set scores are incomplete. Null for other statuses.true
retiredbooleanTrue when a player retired during the match.false
walkoverbooleanTrue when the match was not played (walkover).false
tiebreaksintegerNumber of sets decided by a tiebreak with published tiebreak points.2
set1Player1integerGames won by player 1 in set 1; null when the set was not played.6
set1Player2integerGames won by player 2 in set 1; null when the set was not played.7
set1TiebreakPlayer1integerTiebreak points of player 1 in set 1; null when set 1 had no tiebreak.7
set1TiebreakPlayer2integerTiebreak points of player 2 in set 1; null when set 1 had no tiebreak.9
set2Player1integerGames won by player 1 in set 2; null when the set was not played.7
set2Player2integerGames won by player 2 in set 2; null when the set was not played.6
set2TiebreakPlayer1integerTiebreak points of player 1 in set 2; null when set 2 had no tiebreak.8
set2TiebreakPlayer2integerTiebreak points of player 2 in set 2; null when set 2 had no tiebreak.6
set3Player1integerGames won by player 1 in set 3; null when the set was not played.4
set3Player2integerGames won by player 2 in set 3; null when the set was not played.6
set3TiebreakPlayer1integerTiebreak points of player 1 in set 3; null when set 3 had no tiebreak.
set3TiebreakPlayer2integerTiebreak points of player 2 in set 3; null when set 3 had no tiebreak.
set4Player1integerGames won by player 1 in set 4; null when the set was not played.
set4Player2integerGames won by player 2 in set 4; null when the set was not played.
set4TiebreakPlayer1integerTiebreak points of player 1 in set 4; null when set 4 had no tiebreak.
set4TiebreakPlayer2integerTiebreak points of player 2 in set 4; null when set 4 had no tiebreak.
set5Player1integerGames won by player 1 in set 5; null when the set was not played.
set5Player2integerGames won by player 2 in set 5; null when the set was not played.
set5TiebreakPlayer1integerTiebreak points of player 1 in set 5; null when set 5 had no tiebreak.
set5TiebreakPlayer2integerTiebreak points of player 2 in set 5; null when set 5 had no tiebreak.
resultNotestringESPN one-line summary of the result with seeds and country codes.Tomas Machac (CZE) bt Yannick Hanfmann (GER) 7-6 (9-7) 6-7 (6-8) 6-4
rankintegerRanking rows: current singles rank. Player rows: current rank when the player is in the ESPN top 150 list, otherwise null.1
previousRankintegerRank in the previous ranking release.1
rankChangeintegerPlaces gained since the previous release (negative = places lost).0
rankingPointsintegerRanking points.11000
rankingDatestringISO 8601 date of the ranking release.2026-09-24T07:00:00.000Z
playerIdstringESPN player ID (ranking and player rows).3623
playerNamestringPlayer name (ranking and player rows).Jannik Sinner
firstNamestringFirst name.Jannik
lastNamestringLast name.Sinner
countrystringCitizenship country (ranking and player rows).Italy
countryCodestringThree-letter citizenship code as ESPN publishes it.ITA
ageintegerAge in years as ESPN publishes it.25
birthPlacestringPlace of birth.San Candido, Italy
dateOfBirthstringDate of birth, YYYY-MM-DD.2001-08-16
heightCmintegerHeight in centimetres, converted from the inches ESPN publishes.191
weightKgintegerWeight in kilograms, converted from the pounds ESPN publishes.77
handstringPlaying hand: Right or Left.Right
debutYearintegerYear of the professional debut.2018
activebooleanTrue when ESPN lists the player as active.true
seasonYearintegerSeason the season statistics refer to.2026
seasonStatsAvailablebooleanFalse when ESPN has no season statistics for this player (the season fields are then null).true
seasonSinglesWonintegerSingles matches won this season.44
seasonSinglesLostintegerSingles matches lost this season.3
seasonSinglesTitlesintegerSingles titles won this season.6
seasonDoublesTitlesintegerDoubles titles won this season.0
seasonPrizeMoneyUsdintegerPrize money earned this season in US dollars.11577761
sourceUrlstringESPN page of the tournament, ranking list or player.https://www.espn.com/tennis/scoreboard/tournament/_/eventId/959-2026/competition
foundbooleanTrue on data rows; false on the single status row written when a run returns nothing.true
scrapedAtstringISO 8601 time of the run.2026-09-30T18:00:00.000Z

Sample record

{
  "recordType": "match",
  "id": "186257",
  "tour": "ATP",
  "matchType": "singles",
  "drawName": "Men's Singles",
  "tournamentId": "959",
  "tournamentEventId": "959-2026",
  "tournamentName": "China Open",
  "season": 2026,
  "grandSlam": false,
  "tournamentStartDate": "2026-09-27T04:00:00.000Z",
  "tournamentEndDate": "2026-10-12T03:59:00.000Z",
  "location": "Beijing, China PR",
  "court": "Court 7",
  "round": "Qualifying 1st Round",
  "qualifying": true,
  "startTime": "2026-09-28T03:00:00.000Z",
  "startTimeConfirmed": true,
  "status": "finished",
  "statusDetail": "Final",
  "completed": true,
  "player1Name": "Yannick Hanfmann",
  "player1Id": "3322",
  "player1PlayerIds": "3322",
  "player1Country": "Germany",
  "player1CountryCode": "GER",
  "player1Seed": null,
  "player2Name": "Tomas Machac",
  "player2Id": "3811",
  "player2PlayerIds": "3811",
  "player2Country": "Czechia",
  "player2CountryCode": "CZE",
  "player2Seed": null,
  "winner": 2,
  "winnerName": "Tomas Machac",
  "loserName": "Yannick Hanfmann",
  "setsPlayed": 3,
  "player1SetsWon": 1,
  "player2SetsWon": 2,
  "score": "7-6(9-7) 6-7(6-8) 6-4",
  "scoreConsistent": true,
  "retired": false,
  "walkover": false,
  "tiebreaks": 2,
  "set1Player1": 6,
  "set1Player2": 7,
  "set1TiebreakPlayer1": 7,
  "set1TiebreakPlayer2": 9,
  "set2Player1": 7,
  "set2Player2": 6,
  "set2TiebreakPlayer1": 8,
  "set2TiebreakPlayer2": 6,
  "set3Player1": 4,
  "set3Player2": 6,
  "set3TiebreakPlayer1": null,
  "set3TiebreakPlayer2": null,
  "set4Player1": null,
  "set4Player2": null,
  "set4TiebreakPlayer1": null,
  "set4TiebreakPlayer2": null,
  "set5Player1": null,
  "set5Player2": null,
  "set5TiebreakPlayer1": null,
  "set5TiebreakPlayer2": null,
  "resultNote": "Tomas Machac (CZE) bt Yannick Hanfmann (GER) 7-6 (9-7) 6-7 (6-8) 6-4",
  "rank": 1,
  "previousRank": 1,
  "rankChange": 0,
  "rankingPoints": 11000,
  "rankingDate": "2026-09-24T07:00:00.000Z",
  "playerId": "3623",
  "playerName": "Jannik Sinner",
  "firstName": "Jannik",
  "lastName": "Sinner",
  "country": "Italy",
  "countryCode": "ITA",
  "age": 25,
  "birthPlace": "San Candido, Italy",
  "dateOfBirth": "2001-08-16",
  "heightCm": 191,
  "weightKg": 77,
  "hand": "Right",
  "debutYear": 2018,
  "active": true,
  "seasonYear": 2026,
  "seasonStatsAvailable": true,
  "seasonSinglesWon": 44,
  "seasonSinglesLost": 3,
  "seasonSinglesTitles": 6,
  "seasonDoublesTitles": 0,
  "seasonPrizeMoneyUsd": 11577761,
  "sourceUrl": "https://www.espn.com/tennis/scoreboard/tournament/_/eventId/959-2026/competitionType/1",
  "found": true,
  "scrapedAt": "2026-09-30T18:00:00.000Z"
}

Input

FieldNameTypeWhat it does
dataTypesData typesarrayWhat to return: matches (results and schedule with set and tiebreak scores), rankings (current ATP and WTA singles rankings, top 150 per tour) and players (player profiles with season record and prize money; needs Players). One run can combine several types. Default: matches.
dateFromDate fromstringFirst match date to return, YYYY-MM-DD (UTC). Leave empty to use Last days. Match data is available from about 2010; for older seasons ESPN lists tournaments without matches. Future dates return the order of play that is already published.
dateToDate tostringLast match date to return, YYYY-MM-DD (UTC). Leave empty for today (or for Date from, when Date from is in the future).
lastDaysLast days (when Date from is empty)integerWhen Date from is empty the run covers this many days ending on Date to (today by default). 3 means today and the two days before.
toursToursarrayOptional. ATP (men), WTA (women) and/or Mixed (mixed doubles at the Grand Slams). Leave empty for all. The tour of a match follows its draw, so the women's matches of a combined event such as the China Open are WTA. Rankings exist for ATP and WTA; player profiles are filtered by the tour of the player.
playersPlayersarrayOptional. Player names or ESPN player IDs. Matches: keeps matches in which any listed player plays (the name only has to contain the text, accents and case are ignored, so Zverev matches both Alexander and Mischa Zverev). Rankings: keeps those players. Players data type: returns the profile of every player whose name contains the text among the current ATP/WTA top 150 and the matches read in the run, or of the given ESPN ID (for example 3623).
headToHeadOnlyHead-to-head onlybooleanMatches only: keep just the matches in which two different listed players face each other (for example Players = Sinner, Alcaraz returns their meetings). Needs at least two Players.
tournamentsTournamentsarrayOptional. Keep only matches of tournaments whose name contains one of these texts (for example Wimbledon, Open) or whose ESPN tournament ID equals the value (for example 188 for Wimbledon).
matchTypesMatch typesarrayOptional. singles and/or doubles. Leave empty for both.
matchStatusesMatch statusesarrayOptional. Keep only matches with these statuses: scheduled, in_progress, finished, retired, walkover, postponed, cancelled, suspended. Leave empty for all. Use finished, retired and walkover for completed results only.
includeQualifyingInclude qualifyingbooleanInclude qualifying-round matches. Turn off for main-draw matches only.
maxRankMaximum rank (rankings)integerRankings only: keep players ranked this high or better, for example 10 for the top 10. 0 keeps the whole list (ESPN publishes the top 150 per tour).
onlyNewSinceLastRunOnly rows new since my last runbooleanReturn only rows that earlier runs with the same filters have not delivered to you. The Actor remembers the rows it actually returned, per filter combination (dates excluded, so a scheduled run with Last days keeps one feed), in a storage on your account. A match delivered as scheduled or in progress is delivered again once it is finished; a ranking row again when a new ranking is released. Rows dropped by your filters or cut off by Maximum results are not remembered and can still come later.
oldestFirstOldest firstbooleanReturn matches in chronological order (oldest first) instead of the default newest first. Useful for building a history archive in date order.
maxItemsMaximum resultsintegerStop after this many rows in total. Matches come newest first (see Oldest first), so the limit never cuts off today's results; raise it for long date ranges (a busy month of both tours is 1,300 to 3,000 matches).
proxyConfigurationProxy configurationobjectOptional proxy. Leave disabled: the ESPN JSON API is public and answers requests from data-center addresses.

Call it from your code

Run the Actor and get the results in one request. Replace YOUR_APIFY_TOKEN with the token from your Apify account settings.

curl -X POST "https://api.apify.com/v2/acts/datagrit~tennis-stats-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"dataTypes":["matches"],"lastDays":3,"maxItems":50}'
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('datagrit/tennis-stats-scraper').call({
  "dataTypes": [
    "matches"
  ],
  "lastDays": 3,
  "maxItems": 50
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);

Install with npm i apify-client.

from apify_client import ApifyClient
import os

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("datagrit/tennis-stats-scraper").call(run_input={
  "dataTypes": [
    "matches"
  ],
  "lastDays": 3,
  "maxItems": 50
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(len(items), items[0] if items else None)

Install with pip install apify-client.

Frequently asked questions

Which tournaments are covered?

The ATP and WTA tour events that ESPN lists, from the Grand Slams down to the tour's smaller events, including qualifying and doubles. Challenger and ITF events are not in these feeds. Match data is available from about 2010; for 2005 ESPN lists the tournaments but no matches, and the run status reports tournaments without match data. Tournament names are the names ESPN uses today, also for older editions.

Is court surface, match duration or point-by-point data included?

No. The source publishes scores, sets, tiebreaks, seeds and statuses, not surface, serve statistics, odds or point-by-point data.

How long does a run take?

ESPN returns only the tournaments that start or end inside the requested dates, so the Actor starts its first request 23 days before Date from; that way a tournament already under way, such as the second week of a Grand Slam, is always included, and matches outside your dates are dropped. It asks for one calendar month per tour in a single request and waits about 0.7 seconds between requests. A daily feed of the last three days is two requests; January 2026 for both tours (1,298 matches, including the Australian Open) is two requests; a full season is about 24.

How does the only-new mode work?

The Actor remembers, in a storage on your account, the rows it actually returned to you, separately for each combination of filters. Dates are not part of that combination, so a scheduled run with Last days keeps one feed. A match returned while scheduled or in progress is returned again once it is finished, and a ranking row again when a new ranking is released. Rows dropped by your filters or cut off by Maximum results are not remembered.

How often should I schedule it?

Every few hours during tournaments for a results feed, daily for a history archive. A run that finds nothing new returns one free status row.

Which player IDs can I use?

The ESPN IDs from the `player1Id`, `player2Id` or `playerId` fields, for example 3623 for Jannik Sinner. Names are looked up among the current top 150 of each tour and among the matches read in the same run.

What happens when the source changes?

If the scoreboard changes shape, or set scores, winners, ranking points or season statistics disappear, the run fails with a message instead of returning rows with empty values.

Something looks wrong.

Open an issue with the input you used; changes at the source are fixed quickly.

Try Tennis Stats Scraper - ATP & WTA Scores, Rankings on Apify

Related Actors

Company data

French Company Finder - Sirene Financials

French company lead lists from Sirene screened by net result and revenue, with net margin, size, matching establishment and optional directors.

from $5.60 / 1,000 results
Company data

Poland KRS New Company Registrations Feed

Newly registered Polish companies, foundations and associations from the official KRS court register: NIP, address, PKD, capital, email, with filters and change detection.

from $10.50 / 1,000 results