Tennis Stats Scraper: ATP & WTA Match History, Elo
Pricing
from $6.40 / 1,000 matches
Tennis Stats Scraper: ATP & WTA Match History, Elo
Tennis Abstract match history with serve and return stats for many ATP and WTA players per run, plus profiles, season and surface splits and Elo tables. Filters by date, surface, level, opponent rank. $6.40 per 1,000 rows, no monthly fee.
Pricing
from $6.40 / 1,000 matches
Rating
0.0
(0)
Developer
Misha Ershov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
19 hours ago
Last modified
Categories
Share
Tennis Abstract Scraper: Match Stats, Elo, ATP & WTA
Pulls structured tennis data from Tennis Abstract: full match logs with serve and return statistics for any ATP or WTA player, player profiles, season and surface splits, and the current ATP/WTA Elo rankings (overall, hard, clay, grass).
Give it player names, slugs or Tennis Abstract URLs. Get back clean rows you can drop into a spreadsheet, a model or a betting/analytics pipeline. No login and no API key are needed.
What you get
- Matches (
type: "match"): date, tournament, surface, level (raw code plus a readablelevelName), round, result, score (plussetsandoutcome),matchId, opponent with rank/seed/hand/age data, minutes, and the serve statistics for both players (aces, double faults, first/second serve points won, break points). Derived percentages (ace %, first serve in %, service points won %, return points won %, dominance ratio) are computed for you. Newest matches come first. - Players (
type: "player"): current rank, peak rank, age, hand, backhand, height, country, titles, career match count and the current Elo values. - Seasons (
type: "season"): year-by-year and surface/last-52-week split tables. - Elo (
type: "elo"): the full Tennis Abstract Elo ranking tables for ATP and WTA.
Input
| Field | Type | Default | Description |
|---|---|---|---|
mode | players / elo | players | players scrapes the players you list. elo scrapes the Elo ranking tables only. |
players | list of strings | empty | Names (Jannik Sinner), Tennis Abstract slugs (JannikSinner) or URLs containing p=. Required in players mode. |
tour | auto / atp / wta | auto | auto tries ATP first, then WTA. |
dateFrom, dateTo | YYYY-MM-DD | none | Keep only matches in this date range. |
surfaces | list of Hard, Clay, Grass, Carpet | all | Keep only these surfaces. |
levels | list of level names | all | Keep only these tournament levels: Grand Slam, Masters 1000, Tour 250/500, Tour Finals, Challenger, ITF, Team & Olympics, Other. |
rounds | list of rounds | all | Keep only these rounds: R128, R64, R32, R16, QF, SF, F, RR, BR, Q1, Q2, Q3. |
opponentRankMin, opponentRankMax | integer | 0 (off) | Keep only matches where the opponent's ranking at the time is within this range. Matches with unknown opponent rank are dropped when either is set. If min is above max they are swapped. |
results | all / wins / losses | all | Filter by the player's result. |
includeUpcoming | boolean | false | Also output scheduled matches (result U). |
opponent | string | none | Keep only matches whose opponent name contains this text (case-insensitive). |
maxMatchesPerPlayer | integer 0-5000 | 500 | Cap on match rows per player, newest first. 0 means no match rows (profile and seasons only). |
includeProfile | boolean | true | Output one player row per player. |
includeSeasonStats | boolean | false | Output season rows (yearly and split tables). |
eloTours | list of atp, wta | both | Which Elo tables to read in elo mode. |
maxResults | integer | 1000 | Max Elo rows per tour in elo mode. |
onlyNew | boolean | false | Skip matches already returned by earlier runs with the same state. |
proxyConfiguration | object | Apify proxy | Datacenter proxy is enough. |
Match row fields added for filtering
levelName: readable tournament level, derived from the rawlevelcode (table below).matchId: Tennis Abstract match id,nullfor older matches.sets: list of{winnerGames, loserGames, tiebreak}from the winner's point of view (the same asscore);tiebreakis the loser's tie-break points ornull.outcome:completed,retired,walkover,default, ornullwhen there is no score.
Raw level code | levelName |
|---|---|
G | Grand Slam |
M, PM | Masters 1000 |
A, P, I | Tour 250/500 |
F | Tour Finals |
C | Challenger |
S, any number (ITF prize money in $ thousands) | ITF |
D, O | Team & Olympics |
anything else (for example WTA W) | Other |
Example input:
{"mode": "players","players": ["Jannik Sinner", "Iga Swiatek"],"dateFrom": "2026-01-01","surfaces": ["Grass"],"maxMatchesPerPlayer": 100}
Output examples
Real rows from a live run.
Match row (shortened):
{"type": "match","player": "Jannik Sinner","playerSlug": "JannikSinner","tour": "atp","date": "2026-06-29","tournament": "Wimbledon","surface": "Grass","level": "G","levelName": "Grand Slam","round": "F","result": "W","score": "6-7(7) 7-6(2) 6-3 6-4","sets": [{"winnerGames": 6, "loserGames": 7, "tiebreak": 7}, {"winnerGames": 7, "loserGames": 6, "tiebreak": 2}, {"winnerGames": 6, "loserGames": 3, "tiebreak": null}, {"winnerGames": 6, "loserGames": 4, "tiebreak": null}],"outcome": "completed","bestOf": 5,"rank": 1,"seed": 1,"opponent": "Alexander Zverev","opponentRank": 3,"opponentHand": "R","opponentCountry": "GER","minutes": 226,"aces": 15,"doubleFaults": 2,"servePoints": 136,"firstServesIn": 87,"firstServeWon": 70,"secondServeWon": 32,"acePct": 11,"doubleFaultPct": 1.5,"firstServeInPct": 64,"firstServeWonPct": 80.5,"secondServeWonPct": 65.3,"servicePointsWonPct": 75,"returnPointsWonPct": 30.9,"dominanceRatio": 1.24,"playerUrl": "https://www.tennisabstract.com/cgi-bin/player-classic.cgi?p=JannikSinner","scrapedAt": "2026-10-13T05:46:44Z"}
Player row:
{"type": "player","name": "Carlos Alcaraz","slug": "CarlosAlcaraz","tour": "atp","url": "https://www.tennisabstract.com/cgi-bin/player.cgi?p=CarlosAlcaraz","currentRank": 3,"peakRank": 1,"peakRankDate": "2022-09-12","birthDate": "2003-05-05","age": 23.4,"heightCm": 183,"hand": "R","backhand": "two-handed","country": "ESP","careerMatches": 477,"careerWins": 387,"careerLosses": 90,"titles": 35,"eloRank": 2,"elo": 2171.2,"hardElo": 2111.3,"clayElo": 2098.8,"grassElo": 2026.4,"scrapedAt": "2026-10-13T05:47:22Z"}
Season row:
{"type": "season","table": "tour-years","label": "2026","matches": 47,"wins": 44,"losses": 3,"winPct": 93.6,"holdPct": 94.4,"breakPct": 29.1,"acePct": 14.2,"doubleFaultPct": 2.1,"firstServeInPct": 64.8,"firstServeWonPct": 81.4,"secondServeWonPct": 59.1,"servicePointsWonPct": 73.6,"returnPointsWonPct": 40.7,"totalPointsWonPct": 56.2,"dominanceRatio": 1.54,"player": "Jannik Sinner","slug": "JannikSinner","tour": "atp"}
Elo row:
{"type": "elo","tour": "atp","eloRank": 1,"name": "Jannik Sinner","slug": "JannikSinner","age": 24.8,"elo": 2296.9,"hardEloRank": 1,"hardElo": 2234.3,"clayEloRank": 1,"clayElo": 2186.8,"grassEloRank": 1,"grassElo": 2100.5,"peakElo": 2339.8,"peakMonth": "2026-05","tourRank": 1,"updatedAt": "2026-10-05"}
Pricing events
Rows are charged per type: match, player, season and elo-row. Runs stop cleanly when your spending limit is reached.
FAQ
Where does the data come from? Directly from Tennis Abstract's public pages and data files. Tennis Abstract refreshes its data roughly weekly, so expect "weekly-ish" updates; very recent matches may show up with a delay.
How are player names matched?
A URL with p= always wins. A single token without spaces is treated as the slug. Otherwise the name is matched (accents, case and punctuation ignored) against the ATP and WTA Elo tables, which are downloaded once per run. If there is no match, the capitalized words are joined into a slug (jannik sinner becomes JannikSinner). Players that are not found are logged and counted as notFound; the run still succeeds.
What do Elo, hElo, cElo and gElo mean?
Elo is the overall rating. hElo, cElo and gElo are the surface-specific ratings for hard, clay and grass courts (output fields hardElo, clayElo, grassElo).
Why are some statistics empty?
Detailed serve data exists only for matches Tennis Abstract has charted or received stats for; other rows keep null in those fields.
Missing something? Open an issue - we add fields on request.