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

14 hours ago

Last modified

Categories

Share

Recorded run: 10 ATP ranking rows from 1 ranking list in 13 seconds (2026-10-22, build 0.1.41, run PktYJGWJShCV3h0Oc). A bounded canary sample, not a full-catalogue benchmark.

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

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 rowsscore.sets with tiebreaks, 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, startTime, tournamentName and matchUrl for any day from 7 days back to 7 days ahead (dayOffsets or dateFrom / dateTo).

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.

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.

⚠️ Worth knowing up front

  • Flashscore is the only source. No account, cookies or proxy are used by default. Sofascore and TennisExplorer are not sources.
  • 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.

🧱 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.

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 and eventParticipantId; values the source does not supply stay null.
scoreobjectsets (per-set games, with tiebreak points when played), homeSets and awaySets.
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.

💸 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$12.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 $1.20.

❓ 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 (mcp.apify.com). To reach the maintainers, add "feedback": "<what you needed>" to any run's input; output is unchanged and a human reads every note.

🧰 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-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.