Tennis Point-by-Point Scraper — Rally & Match Stats avatar

Tennis Point-by-Point Scraper — Rally & Match Stats

Pricing

from $18.75 / 1,000 match records

Go to Apify Store
Tennis Point-by-Point Scraper — Rally & Match Stats

Tennis Point-by-Point Scraper — Rally & Match Stats

Point-by-point tennis data no other scraper sells — every serve, rally, and break point of a match, in play order, plus the complete match- and set-level statistics table. Find players by name, then export full match records as JSON, CSV, or Excel. No API key.

Pricing

from $18.75 / 1,000 match records

Rating

0.0

(0)

Developer

SIÁN OÜ

SIÁN OÜ

Maintained by Community

Actor stats

1

Bookmarked

163

Total users

85

Monthly active users

a day ago

Last modified

Share

Tennis Point-by-Point Scraper — Rally & Match Stats 🎾

SIÁN Agency Store Sports Data Scraper Sports Betting Odds Scraper Football API Scraper

🎾 Point by point tennis data — every serve, rally and break point in play order, plus the full tennis match statistics table

Built for the analysts, traders, scouts and developers who model matches rather than read scorelines


🔎 What is the Tennis Point-by-Point Scraper — and when should you use it?

The Tennis Point-by-Point Scraper turns a player name or a tournament name into clean, structured match rows you can filter, export and feed straight into a spreadsheet, database or AI agent. No account, no portal API key, no browser automation to maintain.

Use it when you need: point-by-point rally sequences — sets → games → points, in play order, each carrying the server, the receiver and a break-point flag. It also returns the match and set statistics tables (serve, return, break points, shot quality), match metadata (tournament, city, date, both players), and stable player and tournament IDs to build a roster from.

Use something else when: you want live odds or in-play prices rather than completed-match rally data — use Sports Betting Odds Scraper for that. This Actor covers tennis only. For another sport, use Football API Scraper or Sports Data Scraper.


🤖 Use with AI agents

Already connected to the Apify MCP server? Just ask for this Actor by name: sian.agency/tennis-point-by-point-scraper

Your agent can pay for its own runs. This Actor is eligible for agentic payments. An agent can discover it, run it and settle the bill over x402 (USDC on Base) or Skyfire, with no Apify account or API token of its own. Billing is the same either way: per successful row, never for errors.

Otherwise copy this prompt into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

I want to analyse a tennis player's recent matches using the Apify Actor `sian.agency/tennis-point-by-point-scraper`.
Use it when I need: point-by-point rally data, match and set statistics, match metadata, or stable player and tournament IDs.
Don't use it when: I want live odds or in-play prices — use `sian.agency/sports-betting-odds-scraper` instead. It covers tennis only; for other sports use `sian.agency/football-api-scraper`.
How to call it — pick ONE `operation` per run:
- "discover-players" takes `search` (a name), returns player IDs
- "discover-tournaments" takes `search` (event name or city), returns tournament IDs
- "discover-matches" takes `playerIds`, returns a deduplicated match-ID list (not billed)
- "discover-tournament-matches" takes `tournamentIds`, returns every match ID in the draw (not billed)
- "scrape-matches" takes `playerIds`, returns full match records
- "scrape-match-ids" takes `matchRequests`, returns full match records for IDs I already have
Start with this input:
{
"operation": "scrape-matches",
"playerIds": ["feca401d-e9c2-4638-93b0-878fdb9aea74"],
"maxMatchesPerPlayer": 5,
"maxTotalMatches": 20,
"matchFormat": "singles",
"includePoints": true,
"includeStats": true
}
Ask me which player or tournament I care about, then run the Actor and summarise the results as a table.

Things you can ask your agent for:

  • "Find every Bratislava Open match on file and give me the break-point conversion rate for each player."
  • "Pull Dinev's last ten singles matches and tell me how his first-serve percentage moves between sets."
  • "Build me a CSV of every point in this match, with the server and whether it was a break point."

Machine-readable API, MCP config and OpenAPI definition for this Actor are published at apify.com/sian.agency/tennis-point-by-point-scraper.md.


📋 Overview

Serve, rally, break, repeat. This Actor returns the whole match: player and tournament directories, match metadata, every point of every game in play order, and the complete match- and set-level statistics table. All of it lands as clean, structured rows you can analyse in seconds.

What you get:

  • 🎯 Point-by-point depth: every serve, rally, and break point, in order, ready for modeling
  • 🏆 Whole tournaments: search an event by name or city, then collect every match in the draw
  • Two clicks to data: find a player or a tournament, then pull the matches — no API key, no login
  • 🛡️ Controlled bulk retrieval: explicit match limits, per-match failure isolation, and resumable two-stage jobs
  • 💎 Full statistics table: serve, return, break points, plus performance and shot-quality ratings, at match and set level
  • 🎾 Singles-only mode: filter out doubles before parsing — and never pay for the rows you filtered
  • 🌐 Proxies included: nothing to configure, nothing extra to pay — we route every request through our own residential network
  • Export anywhere: JSON, CSV, or Excel, or straight from the API

✨ Features

  • 🔎 Player Directory Search: find any player by name and get a stable ID
  • 🏆 Tournament Directory Search: find any event by name or host city, then pull its whole draw
  • 🎾 Point-by-Point Data: sets → games → points, in play order
  • 📊 Match & Set Statistics: serve/return/break-point rows plus shot-quality ratings
  • 🏟️ Rich Match Metadata: tournament, location, date, and both players
  • 👥 Multi-Player Runs: scrape many players at once — shared matches are fetched only once
  • 🧭 Two-Stage Actor Operations: discover unique match IDs first, then scrape bounded detail batches
  • 🔌 Stable Retrieval Contract: production clients can plan, deduplicate, and resume outside the Actor
  • 🎛️ Toggle the Payload: include or skip points/statistics per run
  • 📁 Clean Structured Output: one tidy dataset per run, ready for analysis
  • 🔑 No API Key Required: just add input and run

🎬 Quick Start

Scrape a player's matches — points, rallies and statistics — in one call.

curl -X POST "https://api.apify.com/v2/acts/sian.agency~tennis-point-by-point-scraper/runs?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"operation": "scrape-matches", "playerIds": ["feca401d-e9c2-4638-93b0-878fdb9aea74"], "maxMatchesPerPlayer": 5}'

Don't have a player ID yet? Look one up first:

curl -X POST "https://api.apify.com/v2/acts/sian.agency~tennis-point-by-point-scraper/runs?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"operation": "discover-players", "search": "Dinev"}'

Want a whole event instead of one player? Find the tournament, then take its draw:

curl -X POST "https://api.apify.com/v2/acts/sian.agency~tennis-point-by-point-scraper/runs?token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"operation": "discover-tournaments", "search": "Stuttgart"}'

🚀 Getting Started (3 Simple Steps)

Step 1: Hit Start

scrape-matches is the default operation and a player is already prefilled, so a first run returns real matches with full point-by-point data for a few cents. No setup at all.

Step 2: Point it at your own players

Switch to discover-players, search a name, and copy the playerId from the results into playerIds. Then switch back to scrape-matches.

⚠️ Leaving search empty returns the entire directory — about 2,600 players, billed per row. It is capped at maxPlayers (default 100), but keep a name in there unless you deliberately want a big slice.

Want a whole event instead? Run discover-tournaments to find the tournament ID, then discover-tournament-matches to collect every match in the draw, then feed those IDs to scrape-match-ids. The match-list step is not billed.

Step 3: Export your data

Download the dataset as JSON, CSV, or Excel — or pull it straight from the Apify API.

That's it! In minutes, you'll have:

  • A searchable directory of players with stable IDs
  • Full match records with point-by-point data
  • Match and set statistics ready for analysis

📥 Input Configuration

FieldTypeRequiredDescription
operationstringYesdiscover-players, discover-tournaments, discover-matches, discover-tournament-matches, scrape-matches, or scrape-match-ids
searchstringNoFilter the player or tournament directory by name, city, or ID (discover only)
playerIdsarrayNoPlayer IDs for discover-matches or scrape-matches
tournamentIdsarrayNoUp to 500 tournament IDs for discover-tournament-matches
matchRequestsarrayNoUp to 250 {matchId, requestedPlayerIds, requestedTournamentIds} objects for scrape-match-ids
maxPlayersintegerNoHard ceiling on player rows from discover-players; default 100
maxTournamentsintegerNoHard ceiling on tournament rows from discover-tournaments; default 100
maxMatchesPerPlayerintegerNoMatches per player; default 5, 0 = all
maxMatchesPerTournamentintegerNoMatches per tournament draw; default 0 = all
maxTotalMatchesintegerNoHard ceiling on billed matches per run after deduplication; default 20
includePointsbooleanNoInclude nested point-by-point data (default true)
includeStatsbooleanNoInclude match and set statistics (default true)
matchFormatstringNoall (default) or singles; singles mode excludes doubles before point/stat parsing

Find players:

{
"operation": "discover-players",
"search": "Dinev"
}

Scrape matches:

{
"operation": "scrape-matches",
"playerIds": ["feca401d-e9c2-4638-93b0-878fdb9aea74"],
"maxMatchesPerPlayer": 10,
"maxTotalMatches": 50,
"matchFormat": "singles",
"includePoints": true,
"includeStats": true
}

Build a globally deduplicated match plan:

{
"operation": "discover-matches",
"playerIds": ["feca401d-e9c2-4638-93b0-878fdb9aea74"],
"maxMatchesPerPlayer": 25,
"maxTotalMatches": 5000
}

Find tournaments:

{
"operation": "discover-tournaments",
"search": "Berlin"
}

Build a match plan for a whole tournament draw:

{
"operation": "discover-tournament-matches",
"tournamentIds": ["33333333-3333-4333-8333-333333333333"],
"maxMatchesPerTournament": 0,
"maxTotalMatches": 5000
}

Scrape explicit match IDs:

{
"operation": "scrape-match-ids",
"matchRequests": [
{
"matchId": "c292a56a-5fbe-464e-a768-0fc26f483be2",
"requestedPlayerIds": ["feca401d-e9c2-4638-93b0-878fdb9aea74"]
}
],
"matchFormat": "singles"
}

📤 Output

Results are saved to the Apify dataset. Runs return player, tournament, match-reference, match, or match-exclusion records. In singles mode, doubles produce lightweight exclusions after metadata classification and before point/stat parsing. Unknown formats are fully scraped to avoid accidental data loss. Structured per-item failures are stored in the run's FAILURES key-value record.

FieldTypeDescription
recordTypestringplayer, tournament, match-reference, match, or match-exclusion
playerId / fullNamestringPlayer ID and name (player records)
tournamentId / title / citystringTournament ID, event name, and host city (tournament records)
requestedPlayerIdsarrayRequested players that led to a match reference or record
requestedTournamentIdsarrayRequested tournaments that led to a match reference or record
matchId / titlestringMatch ID and title (match records)
matchFormatstringsingles, doubles, or unknown
exclusionReasonstringWhy a match-exclusion record was not fully parsed
tournament / locationstringEvent name and place
completedAtstringWhen the match finished
completedAtUtcstringThe same completion time as a precise UTC timestamp
pointCountnumberTotal points in the match
playerIdsarrayBoth players resolved from point data
setsarrayNested sets → games → points
statsarrayMatch- and set-level statistics rows

Example (match record — real output, trimmed):

{
"recordType": "match",
"matchId": "313b5434-cffc-4938-804c-e6edeec28e8e",
"title": "Ivanov vs Dinev | 2026#3007#QS008 | Tennis Results",
"tournament": "LEXUS NOTTINGHAM CHALLENGER 3",
"matchFormat": "singles",
"pointCount": 146,
"playerIds": ["7626aa33-...", "feca401d-..."],
"sets": [
{ "setNumber": 1, "games": [{ "gameNumber": 1, "points": [
{
"pointNumber": 1,
"setNumber": 1,
"gameNumber": 1,
"winner": "teamA",
"teamServing": "teamA",
"playerServing": "7626aa33-7f05-436e-a24a-282a666310f8",
"playerReceiving": "feca401d-e9c2-4638-93b0-878fdb9aea74",
"rally": [
{ "sequence": 1, "type": "SERVE" },
{ "sequence": 2, "type": "ACE" },
{ "sequence": 3, "type": "POINT_COMPLETE" }
],
"pointBreakdown": {
"outcome": "winner", "serve": "ACE", "totalShots": "1",
"winningShotType": "serve", "errorType": null, "situation": "serving"
},
"ace": true, "isBreakPoint": false, "isDeuce": false,
"streak": { "owner": "teamA", "count": 1 },
"time": "2026-07-06T13:27:53.474Z"
}
] }] }
],
"stats": [
{ "scope": "match", "category": "Points", "metric": "Total Points Won", "teamAValue": "81", "teamBValue": "65" },
{ "scope": "set", "setNumber": "1", "category": "Service", "metric": "Break Points Saved", "teamAValue": "4", "teamBValue": "3" }
]
}

That single match carried 146 points across 3 sets and 84 statistic rows — one billed record.


💼 Use Cases & Examples

1. Tennis Analytics & Modeling

Data scientists building serve/return and momentum models. Input: Player IDs of the players you follow. Output: Point-by-point sequences + statistics. Use: Train win-probability and momentum models on real rally data.

2. Betting & Trading Research

Traders backtesting tennis markets. Input: A roster of players. Output: Historical match results with detailed in-match stats. Use: Validate edges and study live-trading spots.

3. Player Scouting & Coaching

Coaches and scouts preparing for opponents. Input: A player's ID. Output: Full match history with serve/return breakdowns. Use: Build opponent scouting reports and head-to-head views.

4. Sports Media & Content

Journalists and creators covering tournaments. Input: Search a player by name. Output: Match metadata + stats. Use: Fact-check stories and generate match recaps.

5. Data Products & Feeds

Developers powering apps and dashboards. Input: Scheduled runs over a player list. Output: Clean JSON match records. Use: Feed a live tennis stats product without maintaining a scraper.

6. Fantasy & Community Tools

Fantasy platforms and stat communities. Input: A directory of players. Output: Match and point data. Use: Power fantasy scoring and leaderboards.

7. Tournament-Wide Analysis

Researchers studying a single event end to end. Input: A tournament ID from discover-tournaments. Output: Every match in the draw, deduplicated. Use: Compare surface effects, measure how a draw played out, or build an event archive without listing a single player.


🔗 Integration Examples

JavaScript/Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('sian.agency/tennis-point-by-point-scraper').call({
operation: 'scrape-matches',
playerIds: ['feca401d-e9c2-4638-93b0-878fdb9aea74'],
maxMatchesPerPlayer: 10
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0]);

Python

from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('sian.agency/tennis-point-by-point-scraper').call(
run_input={
'operation': 'scrape-matches',
'playerIds': ['feca401d-e9c2-4638-93b0-878fdb9aea74'],
'maxMatchesPerPlayer': 10,
}
)
for item in client.dataset(run['defaultDatasetId']).iterate_items():
print(item)

cURL

curl -X POST 'https://api.apify.com/v2/acts/sian.agency~tennis-point-by-point-scraper/runs?token=YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{"operation": "discover-players", "search": "Dinev"}'

Automation Workflows (N8N / Zapier / Make)

  1. Trigger: Schedule or webhook
  2. HTTP Request: Call the actor API
  3. Process: Handle JSON results
  4. Action: Save, notify, or transform

📊 Performance & Pricing

You pay per record, and only for records you actually receive:

EventWhat it is
Match recordOne complete match — metadata, every point of every game, and the full match & set statistics table
Player recordOne player-directory entry (ID, name, results link)
Tournament recordOne tournament-directory entry (ID, event name, host city, results link)

A match record is an entire match: a typical singles match carries 100–200+ points plus dozens of statistic rows in a single billed row. Doubles excluded by matchFormat: "singles" are never charged, and neither are the match-plan rows from discover-matches or discover-tournament-matches — you only pay for data you asked for and received.

Proxies are on us. There is nothing to configure and nothing extra to pay: every request goes through our own residential proxy network, included in the per-record price. You will not find a proxy field in the input — you never need one.

Free trial vs paid

Free planPaid plan
Players per discover-players run25uncapped
Tournaments per discover-tournaments run25uncapped
Matches per player5uncapped
Matches per run5up to 10,000

The free plan is a real trial, not a demo: same fields, same rally depth, same statistics. Every cap lifts on any paid Apify plan.

Your first run is cheap by design. The defaults scrape a small sample — 5 matches per player, 20 per run — so you can inspect the data shape for pennies before scaling up. maxTotalMatches is a hard ceiling: a run can never bill more matches than that number. Apify compute and storage are billed by Apify under the account that starts the run.

🏗️ Large-Scale Retrieval

For jobs bigger than one run, use the two-stage contract. Call discover-matches with your full player roster — or discover-tournament-matches with a list of tournaments — to get a globally deduplicated match plan. Every unique match ID appears once, no matter how many of your players or events it belongs to. Then submit those IDs to scrape-match-ids in bounded batches of up to 250.

Because the plan is stable and deduplicated, your own client can checkpoint it, resume after an interruption, and re-run only the match IDs it is still missing.


❓ Frequently Asked Questions

Q: How do I get a player's matches? A: Run discover-players first to get the player's ID, then pass that ID to scrape-matches.

Q: Can I scrape a whole tournament? A: Yes. Run discover-tournaments to find the event's ID, discover-tournament-matches to collect every match in the draw, then scrape-match-ids to pull them. Building the match list is not billed.

Q: What are the free-plan limits? A: 25 players or tournaments per discovery run, 5 matches per player, and 5 matches per run, up to 30 runs a day. The fields and rally depth are identical to the paid plan — every cap lifts on any paid Apify plan.

Q: Can I call this Actor from the API on a free plan? A: Run it from the Apify Console or through an MCP client for free. Calling the REST API directly needs any paid Apify plan — that keeps the shared proxy pool healthy for everyone. A free-plan API run finishes cleanly and tells you this, it does not error out.

Q: How many matches can I scrape? A: Defaults are deliberately small — 5 per player, 20 per run — so a first run costs pennies. Raise maxTotalMatches up to 10,000 for a planned job, or split larger ones with discover-matchesscrape-match-ids.

Q: Am I charged for doubles I filtered out? A: No. In singles mode, doubles are classified from match metadata and skipped before any point or statistics parsing, and those exclusion rows are not billed.

Q: Can I get every point of a match? A: Yes — with includePoints enabled, each match returns its sets, games, and points in order.

Q: What statistics are included? A: Serve, return, and break-point stats plus performance and shot-quality ratings, at both match and set level.

Q: What output formats are available? A: JSON, JSONL, CSV, and Excel — export directly from the Apify dataset.

Q: Do I need an account or API key for the source? A: No. Just add your input and run the Actor.

Q: Do I need to set up a proxy? A: No — and there is no proxy field to fill in. Every request is routed through our own residential proxy network at no extra cost to you.

Q: Which players and tournaments are covered? A: The player directory carries thousands of players, and match coverage is strongest on ATP Challenger and ITF-level events. Not every player in the directory has matches on file — in a 31-player sample, 19 had match listings (up to 20 matches each). Run discover-players and then discover-matches to see exactly what is available for your roster before committing to a large scrape; discover-matches plan rows are not charged.


🐛 Troubleshooting

No matches returned for a player

  • Double-check the player ID from discover-players
  • The player genuinely may have no matches on file — the directory lists more players than have match records. Use discover-matches (unbilled) to check a roster before scraping it.

Run stopped at the total-match limit

  • Increase maxTotalMatches deliberately after reviewing the expected number of billed match records

Empty player search

  • Broaden your search term, or leave it empty to return the full directory

Our actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our actors, when used for ethical purposes by Apify users, are safe.

However, you should be aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

You can also read Apify's blog post on the legality of web scraping.


🤝 Support

Telegram Support

Join our active support community


Built by SIÁN Agency | More Tools