← Apify Store
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

FieldTypeDescription
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"
playerIdsarrayšŸŽ¾ **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 []
searchstringšŸ”Ž **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"
maxPlayersintegerThe 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
tournamentIdsarrayšŸ† **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 []
maxTournamentsintegerThe 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
matchRequestsarrayšŸ“‹ **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 []
maxMatchesPerPlayerintegerCap 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
maxMatchesPerTournamentintegerCap 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
maxTotalMatchesintegerThe 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
matchFormatstring (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"
includePointsbooleanKeep 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
includeStatsbooleanKeep 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 120
mcp
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.