Tennis Scraper — Matches, Point-by-Point, Odds, Rankings avatar

Tennis Scraper — Matches, Point-by-Point, Odds, Rankings

Pricing

from $2.50 / 1,000 delivered match or rankings

Go to Apify Store
Tennis Scraper — Matches, Point-by-Point, Odds, Rankings

Tennis Scraper — Matches, Point-by-Point, Odds, Rankings

Flashscore tennis (global, flashscore.com): matches, live and final scores, set and tiebreak scores, point-by-point, match statistics, odds, head-to-head and ATP/WTA singles rankings. By day, tour, tournament or match ID; new-matches-only mode. Pay per result, no login, 20 free previews.

Pricing

from $2.50 / 1,000 delivered match or rankings

Rating

0.0

(0)

Developer

architek.earth

architek.earth

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

19 days ago

Last modified

Features

Limited permissions

Share

Recorded run: 10 ATP ranking rows from 1 ranking list in 10.9 seconds (2026-10-24, build 0.1.49, run C58qKmtlM6IZk80Wc). A bounded canary sample, not a full-catalogue benchmark. Last 30 days (to 2026-10-24): 35 of 36 recorded runs SUCCEEDED (97.2%) and none failed; the other one was a deliberate abort test. Median 10 delivered rows per run (568 rows over 36 runs). Match and ranking rows $2.50 / 1,000; enriched matches (point-by-point, statistics, odds, H2H) $6.00 / 1,000.

architek.earth — an independent data studio. Pay-per-result scrapers with recorded runs, published canaries and stable schemas, built for people and AI agents.

What's new

  • 2026-10-25 — Six input recipes with public example tasks (WTA top 100, yesterday's ATP results, match details by ID added), a head-to-head table and MCP setup for Claude and Cursor added to this page; output and prices unchanged.
  • 2026-10-24 — Match rows now carry resultType (completed, retired, walkover, cancelled, interrupted) with the source's resultNote, the live server and live game score (liveServer, liveGameScore, null when not live), and each player's ATP or WTA singles rank when in the top 100 (schema version 3, add-only). Saved incremental baselines keep working; nothing is re-delivered or re-charged.
  • 2026-10-24 — The $6.00 / 1,000 enriched-match price is live (since 22:37 UTC) and is now the only price the actor accepts; output and other prices unchanged.

Flashscore tennis matches, scores, point-by-point history, statistics, odds and ATP/WTA singles rankings from flashscore.com, by day, tour, tournament or match ID.

SourceMarketFromStandout fields
Flashscore (flashscore.com)ATP, WTA, Challenger, ITF, doubles$2.50 / 1,000 rows; $6.00 / 1,000 enriched matchesscore.sets with tiebreaks, winner, surface, pointByPoint, statistics, odds, rank / points

✅ Why choose this scraper

Tennis results and scores for any day in a 15-day window

Match rows carry homePlayer, awayPlayer, score.sets (with tiebreak points), homeSets / awaySets, status, winner, startTime, endTime, tournamentName, tournamentUrl, surface, resultType (retired, walkover and the rest), liveServer, liveGameScore and matchUrl for any day from 7 days back to 7 days ahead (dayOffsets or dateFrom / dateTo). Each player object adds shortName (three-letter code), slug, photoUrl and the singles rank (top 100).

Point-by-point, match statistics, odds and head-to-head per match

matchDetails mode returns one row per match with pointByPoint (game-by-game point scores), statistics (aces, double faults and the other stats Flashscore lists), odds (home/away prices with opening and active), oddsComparison and h2h, plus a sectionStatus flag for each section, and the match and per-set durations (score.durationMinutes, score.sets[].durationMinutes, every set played).

ATP and WTA singles rankings

Ranking rows give rank, previousRank, points, tournamentsPlayed, playerName, playerId, country and the source date asOf, up to the top 100 per tour.

New matches only, on a schedule

With incrementalMode and a stateKey, a run delivers only match IDs it has not delivered before (changeType: "new"); add emitChanges to also receive changed scores (changeType: "changed"). Unchanged rows are skipped and not charged.

Measured on a schedule

A canary task reads the ATP top 10 every day and a second one reads yesterday's match list; the hero line above is the latest green ranking run.

Tested head-to-head (2026-10-25)

This actorThe field (41 tennis actors in the store search)
Runs that did not succeed0 of 30 recorded canary runs (2026-10-21 to 2026-10-24)median 0%, range 0–100%; 10 of 41 had failed runs (30-day store stats)
Rows per run7.8 rows per canary run (10 ATP ranking rows; 5 of yesterday's matches)not published
Price$2.50 / 1,000 match or ranking rows; $6.00 / 1,000 enriched matchesmedian $2.00 / 1,000, range $0.01–$75.00

⚠️ Worth knowing up front

  • Flashscore is the only source. No account, cookies or proxy are used by default. Sofascore and TennisExplorer are not sources.
  • The field list is a superset of the leading free-tier tennis actors' on the Apify Store (field-by-field check on 2026-10-24) except the tournament round (for example "Semi-finals"), which Flashscore publishes only on its match web page, not in the data feeds this actor reads. Player rank covers the top 100 of the current ATP and WTA singles lists; lower-ranked players and doubles teams get null (the full rank is also only on the match web page). Match and set durations are on matchDetails rows only.
  • winner and endTime are set on finished matches only (endTime is the source's final update, recorded within two minutes of start plus match duration); indoor is true when the tournament header says indoor and null otherwise; surface is hard, clay, grass, carpet or null.
  • Match lists come from Flashscore's day feeds at offsets −7 through +7, counted from UTC today. A feed can carry carryover matches or start times on adjacent UTC dates, so offsets and dates select feeds, not strict start-time filters. Future feeds may be empty. Older results are not reachable through match lists.
  • Live data is polled, not streamed. On 2026-10-20, ten live-match polls all returned HTTP 200 with a median request time of 1.69 seconds; the delay between the court and the feed has not been measured.
  • Detail coverage varies by competition. A requested section the source does not publish is marked NO_DATA in sectionStatus, never invented. Player names and tournament identity on a detail row can stay null when the match is not in an accessible day feed; check identityStatus.
  • Odds are scoped to a source bookmaker and region: the overview uses one bookmaker's home/away full-time market, and oddsComparison uses the observed US-NY route (oddsRegion). active is kept verbatim; an inactive or historical quote is not an executable price.
  • Point-history markers are kept as source markers; their meanings are not guessed. Participant IDs in statistics are event participant IDs, distinct from player IDs. status: "unknown" keeps unverified source status codes instead of labelling them finished or cancelled. Venue, umpire, momentum, doubles rankings and player biographies are not offered.
  • Requests run at most two at a time, at least 500 ms apart. A run stops cleanly 120 seconds before its timeout (6 hours at most), at your spending limit (BUDGET_LIMIT), at maxItems (LIMITED) or on an abort or migration, keeping what it delivered. A run with failed or blocked source requests ends PARTIAL, with the rows it did deliver.
  • When a source request for one input (a day feed, a ranking list or a match ID) fails, is blocked or returns nothing usable, the dataset gets a free status row ("recordType": "status") with status SOURCE_ERROR, BLOCKED, NOT_FOUND or (for a match with none of the requested sections) NO_DATA; a run stopped before its timeout gets one with ACTOR_DEADLINE, and input the actor cannot use gets one with INVALID_INPUT (the run still ends cleanly). The spending limit and a reconciliation stop write no status row: they are reported in the RUN_SUMMARY record and the run's status message (for example Done: 10 rows, 0 details, 0 status rows (COMPLETE)). An empty day feed ends with no rows and no status row.
  • A dataset write and a charge are separate platform operations. If either acknowledgement is uncertain, the run stops with RECONCILIATION_REQUIRED and never repeats the write or charge; the next run with that stateKey reconciles it or stops for a human.

⚡ Try the example

Each Apify account gets 20 free preview runs: one day's match list, up to 10 basic match rows per run, no details or saved state. A failed or empty preview still uses an attempt, and a preview never switches to paid mode.

Open the actor's input page, switch to JSON, paste this and start the run:

{
"mode": "matches",
"billingMode": "preview",
"dayOffsets": [0],
"maxItems": 10
}

Set "billingMode": "paid" and raise maxItems for full day lists, or switch mode to matchDetails with matchIds for point-by-point, statistics and odds.

⚙️ Input

ParameterTypeDefaultDescription
modeselectmatchesmatches (day lists), matchDetails (enriched matches by ID or URL) or rankings (ATP/WTA singles).
billingModeselectpreview (prefill paid)preview (free, matches mode only, one day, up to 10 rows, 20 per account) or paid. Required.
maxItemsinteger100 (prefill 3)Data rows per run, 1–10,000; capped at 10 in preview. Unchanged incremental rows and status rows do not count.
dayOffsetsarray[0]Matches mode. Integers −7 to +7, relative to UTC today. Do not combine with dateFrom / dateTo.
dateFrom / dateTostring—Matches mode. YYYY-MM-DD within seven days of UTC today; selects day feeds, not strict start times.
toursarray[] (all)Matches mode. Any of ATP, WTA, Challenger, ITF, Doubles; a row matching any one is kept.
livebooleanfalseMatches mode. Only matches the source marks as in progress.
tournamentIdsarray—Matches mode. Flashscore tournament IDs (tournamentId on match rows), not names.
matchIds / matchUrlsarray—matchDetails mode. Eight-character Flashscore match IDs or flashscore.com/match/tennis/ URLs, up to 100 each, de-duplicated.
statistics / pointByPoint / odds / h2hbooleantruematchDetails mode. Sections to request; at least one must be on.
tourselectATPRankings mode. ATP or WTA singles.
topNinteger100Rankings mode. Ranking rows, 1–100.
incrementalModebooleanfalseNeeds stateKey. Delivers only IDs not delivered before under this key.
emitChangesbooleanfalseNeeds incrementalMode. Also delivers a row when a seen match or ranking changed.
stateKeystring—Letters, digits, - and _, up to 80. Holds the baseline and saved progress in your account; tied to the inputs it was first used with.
continuePreviousRunbooleanfalseNeeds stateKey. Resumes an unfinished collection with the same inputs.
proxyConfigurationproxynoneOptional. No proxy is used by default.
feedbackstring—Optional note for the maintainers. It is logged and read by a human, and it never changes output.

Unknown input fields are rejected as invalid input.

Input recipes

Each one opens a public example task with exactly this input; run it as is or copy it.

🧱 Stable fields

These top-level fields keep the same name and the same kind of value in every release. Fields are only ever added, never renamed or removed, so an n8n, Make, Zapier or spreadsheet pipeline can bind to them.

Schema version 2 (2026-10-24) added winner, endTime, surface, indoor, tournamentUrl, the player shortName / slug / photoUrl and the detail-row durations. Schema version 3 (2026-10-24) added resultType, resultNote, liveServer, liveGameScore and the player rank. A change in only these fields does not count as a change for emitChanges (a moving live game score or a weekly rank update is not re-delivered), so baselines saved before versions 2 and 3 keep working without re-delivery.

FieldTypeMeaning
matchIdstringEight-character Flashscore match ID on match and detail rows; the join key between them.
sourcestringAlways flashscore.
status / statusCodestringNormalised match state (scheduled, live, finished or unknown) and the original source code.
homePlayer / awayPlayerobjectid, name, country, eventParticipantId, shortName, slug, photoUrl and rank (current ATP or WTA singles rank when the player is in that list's top 100; null otherwise and for doubles); values the source does not supply stay null.
scoreobjectsets (per-set games, with tiebreak points when played), homeSets and awaySets; on detail rows also durationMinutes for the match and for each set.
winnerstring, nullablehome or away on finished matches; null otherwise.
endTimestring, nullableFinished matches: the source's final update time, ISO 8601 UTC.
resultType / resultNotestring, nullableEnded matches: completed, retired, walkover, cancelled or interrupted (other source codes stay null), and the source's note when it gives one (for example "Kudermetova Polina - withdrawn.").
liveServer / liveGameScorestring / object, nullableLive matches only: who is serving (home or away) and the current game score as the source writes it ({"home": "30", "away": "15"}; tiebreak points in a tiebreak); null when the match is not live.
surface / indoorstring / boolean, nullableCourt surface from the tournament header (hard, clay, grass, carpet); indoor true or null.
tournamentUrlstring, nullableFlashscore tournament page.
matchUrlstringFlashscore match page.
scrapedAtstringCollection time, ISO 8601 UTC.
tour / rank / playerId / playerName / points / asOfstring / number / string / string / number / stringRanking rows: tour, position, player, points and the source ranking date (DD.MM.YYYY).
sectionStatusobjectDetail rows: AVAILABLE or NO_DATA for each requested section.
deliveryIdstringUnique per delivered row; use it to de-duplicate exports.
changeTypestring, optionalnew or changed on incremental deliveries.
recordTypestring"status" on free status rows; "match", "matchDetails" or "ranking" on data rows.

Status rows carry recordType, mode, inputRef (the day offset, tour or match ID), status and message. Odds comparisons, statistics entries and point markers are source passthrough data. Other fields are extras, not part of the contract. To have one pinned, say so in feedback.

📦 Output example

Abridged real row from run 630yJzyiYY5Lon3ex on 2026-10-22; values are a historical snapshot.

{
"recordType": "match",
"matchId": "zHfvs0hR",
"source": "flashscore",
"tour": "ATP",
"competitionLabel": "ATP - Singles",
"isDoubles": false,
"tournamentName": "ATP - SINGLES: Almaty (Kazakhstan), hard (indoor)",
"tournamentId": "KrjEivYq",
"startTime": "2026-10-21T06:40:00+00:00",
"status": "finished",
"statusCode": "3",
"homePlayer": {"id": "M9pojLqi", "name": "Bellucci M.", "country": "Italy", "eventParticipantId": "CjiJR5re"},
"awayPlayer": {"id": "AXL5qpKa", "name": "Etcheverry T. M.", "country": "Argentina", "eventParticipantId": "ImfRPqD7"},
"score": {
"sets": [
{"set": 1, "home": 7, "away": 6, "tiebreak": {"home": 7, "away": 5}},
{"set": 2, "home": 2, "away": 6},
{"set": 3, "home": 6, "away": 4}
],
"homeSets": 2,
"awaySets": 1
},
"matchUrl": "https://www.flashscore.com/match/tennis/zHfvs0hR/",
"scrapedAt": "2026-10-22T09:39:05.781481+00:00"
}

A ranking row from canary run PktYJGWJShCV3h0Oc on the same day:

{"recordType": "ranking", "tour": "ATP", "type": "singles", "asOf": "19.10.2026", "playerName": "Sinner Jannik", "playerId": "6HdC3z4H", "country": "Italy", "rank": 1, "previousRank": 1, "points": 10950, "tournamentsPlayed": 17}

A free status row looks like this (a bogus match ID next to five real ones, run FEsJv52eMo9R5vMjt, 2026-10-20):

{"recordType": "status", "mode": "matchDetails", "inputRef": "ZZZZZZZZ", "status": "NOT_FOUND", "message": "Source returned HTTP 404"}

🧠 Advanced usage

Get new ATP and WTA results every day

Schedule mode: "matches" with dayOffsets: [-1, 0], tours: ["ATP", "WTA"], incrementalMode: true and a stateKey. Each run delivers only matches it has not delivered before; add emitChanges: true from the first run of a new key to also receive score changes as extra rows. Check that RUN_SUMMARY.status is COMPLETE before treating a day as finished.

Point-by-point and odds for a list of matches

Collect matchId values from a match-list run and send the ones you need to matchDetails (up to 100 per run). One detail row is charged once, whatever sections it carries; a match with none of the requested sections gets a free NO_DATA status row instead.

Resume a run that hit its limit

Keep the same inputs and stateKey, set continuePreviousRun: true, and raise maxItems or the spending limit. The saved queue is used before another feed is fetched; resumed date collections keep their original date and refuse to run outside the seven-day window.

Track the ATP and WTA rankings weekly

Run mode: "rankings" once per tour each week; asOf carries the source ranking date (19.10.2026 on the 2026-10-22 runs) and previousRank the prior position. Rankings are source-dated snapshots, so intraday repeats add little.

🔌 Integrations

Results land in a dataset you can export as JSON, CSV or Excel. They can also flow to Google Sheets, Make, Zapier or n8n, or to a webhook when a run finishes. The Apify Scheduler runs it on a timetable. Daily with incrementalMode catches each day's new results; weekly suits rankings. Copy-paste code in Python, Node, curl and n8n: architek-earth/examples/tennis-data-scraper.

n8n and Make

💸 Pricing

Pay per result. Platform and proxy usage are included. Status rows and failed requests are free. Each delivered row is charged once, after it is written, with an idempotency key; a run resumed with continuePreviousRun and the same stateKey skips rows already delivered and does not charge them again, and unchanged rows in incrementalMode are free.

EventPriceWhat it covers
Free preview$020 runs per Apify account; one day's match list, up to 10 basic match rows per run.
result$2.50 / 1,000 rowsOne delivered match or ranking row, including a changed snapshot with emitChanges.
match-detail$6.00 / 1,000 matchesOne delivered matchDetails row with its available requested sections; never charged together with result.

100 ATP ranking rows cost $0.25; 100 enriched matches cost $0.60. The Apify free plan's $5 monthly credit covers about 2,000 match or ranking rows at $2.50 / 1,000 (or about 833 enriched matches), on top of the 20 free previews.

❓ FAQ

Is there a Flashscore API for tennis scores? This actor works as one: it returns Flashscore tennis matches, scores, point-by-point, statistics, odds and rankings as JSON through the Apify API, callable from any language or from n8n, Make and Zapier.

Do I need a Flashscore account or a proxy? No. No account, cookies or API key, and no proxy by default.

Can I get point-by-point data and match statistics? Yes, in matchDetails mode: pointByPoint and statistics, when Flashscore publishes them for that match. sectionStatus says which sections were available.

Does it return tennis betting odds? Yes, in matchDetails mode: odds (home/away prices with opening and active) and oddsComparison from the US-NY route. They are source quotes for one region, not executable prices.

Can I get live tennis scores? Yes, by polling: live: true keeps only in-progress matches. It is not a stream, and the court-to-feed delay has not been measured.

Can I get WTA, doubles or historical rankings? ATP and WTA singles, up to the top 100, as of the current source date. Doubles rankings and past ranking weeks are not available.

🤖 For AI agents & LLM apps

Paste this into your AI assistant:

architekture/tennis-data-scraper on Apify. Python: from apify_client import ApifyClient; client = ApifyClient("TOKEN"); run = client.actor("architekture/tennis-data-scraper").call(run_input={"mode": "matches", "billingMode": "paid", "dayOffsets": [0], "maxItems": 3}); items = client.dataset(run["defaultDatasetId"]).list_items().items (page with offset/limit for large exports). Rows with recordType == "status" are free and explain a failed, blocked or empty source request or a stop before the timeout; the run outcome (COMPLETE, LIMITED, PARTIAL, BUDGET_LIMIT, INVALID_INPUT, RECONCILIATION_REQUIRED) is in the RUN_SUMMARY record of the run's key-value store. Full input schema and README: GET https://api.apify.com/v2/acts/architekture~tennis-data-scraper/builds/default. Token: https://console.apify.com/settings/integrations. The actor is also reachable through the Apify MCP server: https://mcp.apify.com?tools=architekture/tennis-data-scraper (ready-to-paste config: https://github.com/architek-earth/mcp). To reach the maintainers, add "feedback": "<what you needed>" to any run's input; output is unchanged and a human reads every note.

MCP config for Claude Desktop and Cursor

Claude Desktop (or claude.ai): Settings, Connectors, Add custom connector, URL https://mcp.apify.com?tools=architekture/tennis-data-scraper; it signs you in to Apify with OAuth. Cursor (~/.cursor/mcp.json), with your token in the APIFY_TOKEN environment variable:

{
"mcpServers": {
"architekture-tennis-data-scraper": {
"type": "http",
"url": "https://mcp.apify.com?tools=architekture/tennis-data-scraper",
"headers": { "Authorization": "Bearer ${env:APIFY_TOKEN}" }
}
}
}

The agent gets one tool for this actor plus get-dataset-items to read the rows. A typical prompt: "yesterday's ATP results with set scores"; start with "billingMode": "preview" (free) and set a spending cap (maxTotalChargeUsd) in the run options when your client offers it.

🧰 More from architek.earth

Sports

  • Tennis Scraper — you are here

Search, travel and trends

Full catalogue: github.com/architek-earth.

architek.earth · code like concrete

Questions, or a field you need that is not here? Open an issue on the actor's Issues tab or email north@architek.earth. A human replies within a day.

This actor collects only publicly available data from flashscore.com, the same pages any visitor sees without logging in. It does not bypass authentication. It is not affiliated with or endorsed by Flashscore or the tours whose data appears there. You are responsible for how you use the output, including the site's terms, copyright, local gambling regulations, and privacy law such as GDPR and CCPA where they apply.

📝 Changelog

  • 2026-10-25 — [internal] Studio core 1.0.6: reconciliation diagnostics; source retry policy, schema and pricing unchanged.

  • 2026-10-25 — Six input recipes with public example tasks (WTA top 100, yesterday's ATP results, match details by ID added), a head-to-head table and MCP setup for Claude and Cursor added to this page; output and prices unchanged.

  • 2026-10-24 — Match rows now carry resultType (completed, retired, walkover, cancelled, interrupted) with the source's resultNote, the live server and live game score (liveServer, liveGameScore, null when not live), and each player's ATP or WTA singles rank when in the top 100 (schema version 3, add-only). Saved incremental baselines keep working; nothing is re-delivered or re-charged.

  • 2026-10-24 — The $6.00 / 1,000 enriched-match price is live (since 22:37 UTC) and is now the only price the actor accepts; output and other prices unchanged.

  • 2026-10-24 — Enriched match rows (match-detail) now cost $6.00 per 1,000, down from $12.00. Match and ranking rows stay at $2.50 per 1,000.

  • 2026-10-24 — Match rows now carry winner, endTime, surface, indoor and tournamentUrl, player objects add shortName, slug and photoUrl, and detail rows add match and per-set durations (schema version 2, add-only). Saved incremental baselines keep working; nothing is re-delivered or re-charged.

  • 2026-10-22 — Invalid input now writes a free INVALID_INPUT status row. A challenge page served instead of feed data is reported as BLOCKED on the first response.

  • 2026-10-22 — Status message now reads Done: N rows, M details, K status rows (STATUS).

  • 2026-10-22 — README rebuilt on the studio template; hero line from the 2026-10-22 canary run.

  • 2026-10-21 — Runs stop cleanly 120 seconds before their timeout (6 hours at most) with a free ACTOR_DEADLINE status row and can be resumed with continuePreviousRun, instead of timing out.

  • 2026-10-20 — Owner approved public release with the documented live-freshness limitation. Free preview, paid ranking and recurring monitoring examples prepared; schedules remain off.

  • 2026-10-20 — Private Flashscore-only v1 candidate: matches, detail feeds, singles rankings, lifetime preview enforcement, saved continuation and incremental baselines. Publication held pending product validation and owner approval.