AS
Apify Store
/sian.agency/tennis-point-by-point-scraper
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 e
liveasyncunknown~30s typical
sportsautomationdeveloper toolstennispoint
$0.094
per result + $0.005 call
plus $0.001 platform fee per run
Actor charges $0.075 per result; Cracked adds 25% plus a $0.005 call fee
Bring your own Apify Store key: provider portion drops to $0.
$Set up https://cracked-ai-pearl.vercel.app/SKILL.md, then use Cracked to run apify /sian.agency/tennis-point-by-point-scraper for me.
Input
| Field | Type | Description |
|---|---|---|
| operation* | string (scrape-matches | discover-players | discover-tournaments | discover-matches | discover-tournament-matches | scrape-match-ids) | šÆ **Pick one operation per run.** Each run produces one clean dataset matching the chosen mode. - **š¾ Scrape a player's matches** ā the main event. Full matches: every point in play order plus the complete statistics table - **š Find players** ā look up a player by name to get their ID - **š Find tournaments** ā look up a tournament by name or city to get its ID - **š Build a match list** ā collect unique match IDs for a roster without scraping them yet (not billed) - **š Build a tournament match list** ā collect every match ID from a tournament's draw without scraping them yet (not billed) - **š Scrape specific matches** ā scrape match IDs you already have š” **Just hit Start.** A player is already filled in below, so a default run returns real matches straight away. š **Scraping someone else?** Switch to `Find players`, search their name, copy the `playerId` from the results into š¾ Player IDs, then switch back here. š§ **For big jobs:** build a match list first ā it deduplicates matches shared between players so you never pay for the same match twice ā then feed the IDs to `Scrape specific matches` in batches. default "scrape-matches" |
| playerIds | array | š¾ **Required for `Scrape a player's matches` and `Build a match list`.** Ignored by the other operations. Paste one or more player IDs from your `Find players` results. Add as many as you like ā up to 500 ā and a match shared by two of them is fetched, and billed, only once. š” Not every player in the directory has matches on file; coverage is strongest on ATP Challenger and ITF events. Run `Build a match list` first to see what exists for your roster without being billed for it. default [] |
| search | string | š **Used by `Find players` and `Find tournaments`.** Ignored by every other operation. For players, type part of a name. For tournaments, type part of the event name or its host city. Matching is partial and case-insensitive, so `dinev` and `Dinev` both work. ā ļø **Leave this empty and you get the whole directory ā about 2,600 players, billed per row.** Keep a name here unless you deliberately want the full list. š” Copy the `playerId` from your results into š¾ Player IDs. default "Dinev" |
| maxPlayers | integer | The hard ceiling on player rows returned by `Find players`. This is your spend guard: every player row is billed, and an empty name field matches the whole directory of roughly 2,600 players. Raise it when you genuinely want a bigger slice ā up to 10,000. default 100 |
| tournamentIds | array | š **Required for `Build a tournament match list`.** Ignored by every other operation. Paste one or more tournament IDs from your `Find tournaments` results ā up to 500. Every match in each draw is collected, and a match shared by two tournaments is listed only once. š” The resulting match IDs are not scraped yet. Feed them to `Scrape specific matches` when you are ready to pull the point-by-point data. default [] |
| maxTournaments | integer | The hard ceiling on tournament rows returned by `Find tournaments`. This is your spend guard: an empty name field matches the entire tournament directory. Raise it when you genuinely want a bigger slice ā up to 10,000. default 100 |
| matchRequests | array | š **Required for `Scrape specific matches`.** Ignored by every other operation ā most users never touch this field. Use it only when you already hold match IDs, typically from a `Build a match list` run, and want to scrape them in controlled batches. Up to 250 per run. Each entry takes a `matchId`, and optionally the players it belongs to: ```json [{ "matchId": "313b5434-cffc-4938-804c-e6edeec28e8e" }] ``` š” **Scraping one player?** Ignore this and use `Scrape a player's matches` instead. default [] |
| maxMatchesPerPlayer | integer | Cap how many matches to take from each player's history. Set `0` to take every match on file. Starts at `5` so your first run is quick and cheap. Raise it once you have seen the data ā there is no upper limit beyond the total cap below. default 5 |
| maxMatchesPerTournament | integer | Cap how many matches to take from each tournament's draw when running `Build a tournament match list`. Set `0` to take every match in the draw. Only this operation reads it ā a player scrape uses `Matches per player` above. default 0 |
| maxTotalMatches | integer | The hard ceiling on matches scraped in one run, applied after duplicates are removed. This is your spend guard: a run can never bill more matches than this number. Raise it deliberately for a planned bulk retrieval ā up to 10,000. default 20 |
| matchFormat | string (all | singles) | Choose `Singles only` to skip doubles matches. Doubles are identified from match metadata and dropped before any point or statistics parsing, so **you are never billed for a match this filter removes**. default "all" |
| includePoints | boolean | Keep this on for the rally-level data ā sets, games, and every point in play order, each with server, receiver, shot sequence, and break-point flags. Turn it off for a lighter payload when you only want scorelines and statistics. The price per match is the same either way. default true |
| includeStats | boolean | Keep this on for the full statistics table ā serve, return, break points, and shot-quality ratings, at both match and set level. A typical match carries 40ā90 statistic rows. Turn it off for a lighter payload. The price per match is the same either way. default true |
Call it
curl
curl https://cracked-ai-pearl.vercel.app/v1/run \
-H "Authorization: Bearer ck_live_..." -H "content-type: application/json" \
-d '{"provider":"apify","endpoint":"/sian.agency/tennis-point-by-point-scraper","input":{"operation":"scrape-matches","playerIds":["feca401d-e9c2-4638-93b0-878fdb9aea74"],"search":"Dinev","tournamentIds":["33333333-3333-4333-8333-333333333333"]},"wait":false}'poll
curl "https://cracked-ai-pearl.vercel.app/v1/runs/<runId>?wait=30" -H "Authorization: Bearer ck_live_..."cli
npx cracked-ai run -p apify -e /sian.agency/tennis-point-by-point-scraper -i '{"operation":"scrape-matches","playerIds":["feca401d-e9c2-4638-93b0-878fdb9aea74"],"search":"Dinev","tournamentIds":["33333333-3333-4333-8333-333333333333"]}' -w 120mcp
run_tool({ provider: "apify", endpoint: "/sian.agency/tennis-point-by-point-scraper", input: {"operation":"scrape-matches","playerIds":["feca401d-e9c2-4638-93b0-878fdb9aea74"],"search":"Dinev","tournamentIds":["33333333-3333-4333-8333-333333333333"]}, wait: false })Try it
Runs against your signed-in workspace balance. Sign in if you have not.