Tennis Abstract Scraper: Player Stats & Match Results avatar

Tennis Abstract Scraper: Player Stats & Match Results

Pricing

from $2.00 / 1,000 match row returneds

Go to Apify Store
Tennis Abstract Scraper: Player Stats & Match Results

Tennis Abstract Scraper: Player Stats & Match Results

Tennis Abstract scraper: one row per match (date, tournament, surface, round, opponent, score, rankings, serve and return stats) plus a player summary with win % by surface. ATP and WTA. New-match alerts in Slack, Discord or Telegram. No login; unknown players are free.

Pricing

from $2.00 / 1,000 match row returneds

Rating

0.0

(0)

Developer

Adriana Voss

Adriana Voss

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Type a player's name, get their recent matches from Tennis Abstract, one row per match: date, tournament, surface, round, opponent, result, score, both players' rankings, and the serve and return numbers the site publishes (aces, double faults, first-serve %, break points, dominance ratio). Add a player summary row with current and peak rank, country, and win % overall and by surface. ATP and WTA players both work.

Switch on monitoring and the actor remembers which matches each player had last time, returns only the new ones, and posts them to Slack, Discord or Telegram.

Who uses this

  • Bettors and modellers who need recent form, surface splits and serve / return rates per player, as rows they can load into a spreadsheet or a model.
  • Fantasy players and tipsters who want to know when a player's tournament run moves on, without opening a page.
  • n8n / Make / Clay builders and sports-data hobbyists who want Tennis Abstract's numbers inside a workflow, without a login or an API key.

Try it in 30 seconds

Press Start with the pre-filled input. It reads Novak Djokovic's page and returns his player summary plus his most recent matches:

{ "players": ["Novak Djokovic"], "maxMatchesPerPlayer": 50 }

Write your own lines like this, one player per line:

Novak Djokovic
Iga Swiatek
Felix Auger-Aliassime
https://www.tennisabstract.com/cgi-bin/player.cgi?p=CarlosAlcaraz

Names are matched the way Tennis Abstract spells them: first and last name, accents and hyphens dropped (Auger-Aliassime becomes FelixAugerAliassime on the site, and you can type either). A pasted player link is read as text for the name.

What you get

Two kinds of row, told apart by rowType:

  • match-row: player, tour, date, tournament, surface, level, round, result (W or L), opponent, opponentCountry, score (winner's games first), bestOf, playerRank, opponentRank, minutes, aceRatePct, doubleFaultRatePct, firstServeInPct, firstServeWonPct, secondServeWonPct, breakPointsSaved, breakPointsFaced, dominanceRatio, and for players with a current history file also aces, doubleFaults, servePoints and returnPointsWonPct. source says where the row came from.
  • player-row: player, tour, country, birthYear, hand, currentRank, peakRank, eloRating, eloRank, then arithmetic over the matches in the sample: matchesPlayed, wins, losses, winPct, sampleFrom, sampleTo, and matches and win % on hard, clay, grass and carpet.

Filters are applied after the fetch: since (a date), surface, maxMatchesPerPlayer. The player summary then covers exactly the matches those filters leave.

Example output

{
"rowType": "match-row",
"player": "Novak Djokovic",
"tour": "atp",
"date": "2026-10-07",
"tournament": "Shanghai Masters",
"surface": "Hard",
"round": "R64",
"result": "L",
"opponent": "Hubert Hurkacz",
"opponentCountry": "POL",
"score": "6-4 6-3",
"playerRank": 8,
"opponentRank": 34,
"minutes": 87,
"aceRatePct": 3.6,
"firstServeInPct": 53.6,
"dominanceRatio": 0.72,
"source": "recent-results"
}

Monitoring: new-match alerts in three steps

  1. Tick Track players across runs and give the watchlist a name (deltaName), for example my-players.
  2. Tick Only return new matches so a run with nothing new costs nothing, and paste a Slack, Discord or Telegram link into Send alerts to.
  3. Save the run as a task and schedule it daily. The first run stores a baseline (tick Seed silently to also skip billing it); from the second run on, every new match arrives as a row and as one message per player.

The watchlist is kept in a named store under your account, so it survives between scheduled runs.

Limits, stated plainly

  • Data as Tennis Abstract publishes it. Matches, rankings and serve numbers are the site's own. There are no live scores, no betting odds, no point-by-point.
  • How far back depends on the player. Every player comes with about the last 20 matches from the site's Recent Results table. Some players also have a current match-history file that reaches back further (up to roughly 140 matches); for others that file is out of date, and the row says historyDepth: "recent-only" rather than mixing in years-old matches across a gap.
  • Dates are tournament start dates. Tennis Abstract dates every round of a tournament with the day it started.
  • Serve and return stats come with each match when the site has them; older or lower-level matches can have blanks.
  • Names must match the site. A player Tennis Abstract does not list, or a misspelled name, comes back as not found (free). Paste the player link if a name does not match.
  • About 25 seconds per player. Each player takes three requests to the site, one second apart at the fastest, and the platform's proxy adds a few seconds to each. A run holds up to 150 players, but the run's time limit decides how many it gets through: players it has no time for come back as free "not checked" rows, so run them again.
  • Misses are free. Not found, blocked, layout change: nothing is charged.

How to use

  1. In the Apify Console. Open the actor page and click Start — the players field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
  2. Via the API. Call it directly with a POST request — no Console needed once you have an API token:
    curl "https://api.apify.com/v2/acts/accountable_eel~tennis-abstract-stats-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"players":["Novak Djokovic"]}'
  3. On a schedule. Save this actor as an Apify Task with the input you want, then add a Schedule (hourly, daily, weekly) so it runs on its own — no server of your own required.

Input

{
"players": [
"Novak Djokovic"
]
}

One player per line: the first and last name ("Novak Djokovic", "Iga Swiatek"; accents and hyphens are fine) or a tennisabstract.com player link. ATP and WTA players both work.. Plan on about 25 seconds per player; players the run has no time for come back as free "not checked" rows. Accepted formats: Novak Djokovic, Iga Swiatek, Felix Auger-Aliassime, https://www.tennisabstract.com/cgi-bin/player.cgi?p=CarlosAlcaraz.

{
"items": ["<identifier-1>", "<identifier-2>"],
"maxConcurrency": 5
}

items is a list of ATP or WTA player names or Tennis Abstract player links. One dataset row is returned per item; rows with "found": false are never charged.

Output

One row per item, for example:

queryfoundstatusrowTypeplayertourcountrybirthYearhandcurrentRankpeakRankeloRatingeloRankmatchesPlayedwinslosseswinPctsampleFromsampleTohardMatcheshardWinPctclayMatchesclayWinPctgrassMatchesgrassWinPctcarpetMatchescarpetWinPcthistoryDepthdatetournamentsurfacelevelroundresultopponentopponentCountryscorebestOfplayerRankopponentRankminutesacesdoubleFaultsservePointsaceRatePctdoubleFaultRatePctfirstServeInPctfirstServeWonPctsecondServeWonPctbreakPointsSavedbreakPointsFacedreturnPointsWonPctdominanceRatiosourceisNewMatchplayerUrlcheckedAtchangeTypenewMatchCountdaysSincePreviousnotifyErrorsscrapedAt
Novak DjokovictrueOK<win %><hard-court win %><clay win %><grass win %><carpet win %><ace %><double fault %><first serve in %><first serve points won %><second serve points won %><return points won %>1970-01-01T00:00:00.000Z

A miss comes back as a row with "found": false and is never charged.

Pricing

Pay-per-event. A flat per-run fee covers session/proxy warmup; you're billed per item only when data is actually found and returned — see .actor/pay_per_event.json for exact prices. A miss is never charged.

Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

curl "https://api.apify.com/v2/acts/accountable_eel~tennis-abstract-stats-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
-X POST \
-H "Content-Type: application/json" \
-d '{"players":["Novak Djokovic"]}'

n8n. Add an HTTP Request node: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~tennis-abstract-stats-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body Content Type JSON, JSON Body {"players":["Novak Djokovic"]} (swap in an expression from an earlier node for a real value).

Clay. Add an "HTTP API" column: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~tennis-abstract-stats-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body {"players":["{{player}}"]}, mapping the row's player into the players array.

MCP. In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Tennis Abstract Scraper | Apify" — the agent will find and run this actor.

FAQ

Is this an official Tennis Abstract API? No. It reads the public player pages and the data files those pages load, without logging in. It is not affiliated with Tennis Abstract.

ATP and WTA? Both. tour on every row says which. Type the name; there is no tour setting to get wrong.

Why do some players have only about 20 matches? The site's Recent Results table holds roughly the last 20 matches for everyone. A deeper history file exists for many players, but for some of them it has stopped updating; the actor only uses it when it joins up with the recent matches.

What does the dominance ratio mean? Tennis Abstract's DR: the share of return points won divided by the share of serve points lost. Above 1, the player won more return points than serve points they lost.

Why is the date the same for every round of a tournament? The site dates each match with the tournament's start date. Use round to order matches inside a tournament; rows are already sorted newest first.

What costs money? Each match row and each player summary row you receive. Players that are not found, blocked or unreadable cost nothing, and with Only return new matches a player with nothing new costs nothing.