# Tennis Scraper — Matches, Point-by-Point, Odds, Rankings (`architekture/tennis-data-scraper`) Actor

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.

- **URL**: https://apify.com/architekture/tennis-data-scraper.md
- **Developed by:** [architek.earth](https://apify.com/architekture) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 delivered match or rankings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

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

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

| Source | Market | From | Standout fields |
|---|---|---|---|
| Flashscore (flashscore.com) | ATP, WTA, Challenger, ITF, doubles | $2.50 / 1,000 rows | `score.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:

```json
{
  "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

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | select | `matches` | `matches` (day lists), `matchDetails` (enriched matches by ID or URL) or `rankings` (ATP/WTA singles). |
| `billingMode` | select | `preview` (prefill `paid`) | `preview` (free, matches mode only, one day, up to 10 rows, 20 per account) or `paid`. Required. |
| `maxItems` | integer | `100` (prefill `3`) | Data rows per run, 1–10,000; capped at 10 in preview. Unchanged incremental rows and status rows do not count. |
| `dayOffsets` | array | `[0]` | Matches mode. Integers −7 to +7, relative to UTC today. Do not combine with `dateFrom` / `dateTo`. |
| `dateFrom` / `dateTo` | string | — | Matches mode. `YYYY-MM-DD` within seven days of UTC today; selects day feeds, not strict start times. |
| `tours` | array | `[]` (all) | Matches mode. Any of `ATP`, `WTA`, `Challenger`, `ITF`, `Doubles`; a row matching any one is kept. |
| `live` | boolean | `false` | Matches mode. Only matches the source marks as in progress. |
| `tournamentIds` | array | — | Matches mode. Flashscore tournament IDs (`tournamentId` on match rows), not names. |
| `matchIds` / `matchUrls` | array | — | matchDetails mode. Eight-character Flashscore match IDs or `flashscore.com/match/tennis/` URLs, up to 100 each, de-duplicated. |
| `statistics` / `pointByPoint` / `odds` / `h2h` | boolean | `true` | matchDetails mode. Sections to request; at least one must be on. |
| `tour` | select | `ATP` | Rankings mode. `ATP` or `WTA` singles. |
| `topN` | integer | `100` | Rankings mode. Ranking rows, 1–100. |
| `incrementalMode` | boolean | `false` | Needs `stateKey`. Delivers only IDs not delivered before under this key. |
| `emitChanges` | boolean | `false` | Needs `incrementalMode`. Also delivers a row when a seen match or ranking changed. |
| `stateKey` | string | — | Letters, digits, `-` and `_`, up to 80. Holds the baseline and saved progress in your account; tied to the inputs it was first used with. |
| `continuePreviousRun` | boolean | `false` | Needs `stateKey`. Resumes an unfinished collection with the same inputs. |
| `proxyConfiguration` | proxy | none | Optional. No proxy is used by default. |
| `feedback` | string | — | 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.

| Field | Type | Meaning |
|---|---|---|
| `matchId` | string | Eight-character Flashscore match ID on match and detail rows; the join key between them. |
| `source` | string | Always `flashscore`. |
| `status` / `statusCode` | string | Normalised match state (`scheduled`, `live`, `finished` or `unknown`) and the original source code. |
| `homePlayer` / `awayPlayer` | object | `id`, `name`, `country` and `eventParticipantId`; values the source does not supply stay null. |
| `score` | object | `sets` (per-set games, with `tiebreak` points when played), `homeSets` and `awaySets`. |
| `matchUrl` | string | Flashscore match page. |
| `scrapedAt` | string | Collection time, ISO 8601 UTC. |
| `tour` / `rank` / `playerId` / `playerName` / `points` / `asOf` | string / number / string / string / number / string | Ranking rows: tour, position, player, points and the source ranking date (`DD.MM.YYYY`). |
| `sectionStatus` | object | Detail rows: `AVAILABLE` or `NO_DATA` for each requested section. |
| `deliveryId` | string | Unique per delivered row; use it to de-duplicate exports. |
| `changeType` | string, optional | `new` or `changed` on incremental deliveries. |
| `recordType` | string | `"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.

```json
{
  "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:

```json
{"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):

```json
{"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](https://github.com/architek-earth/examples/tree/main/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.

| Event | Price | What it covers |
|---|---|---|
| Free preview | $0 | 20 runs per Apify account; one day's match list, up to 10 basic match rows per run. |
| `result` | $2.50 / 1,000 rows | One delivered match or ranking row, including a changed snapshot with `emitChanges`. |
| `match-detail` | $12.00 / 1,000 matches | One 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**

- [Google Trends Scraper](https://apify.com/architekture/google-trends-scraper) — Trending Now by country, traffic, related queries
- [Google Hotels Scraper](https://apify.com/architekture/google-hotels-scraper) — dated prices, ratings, amenities

Full catalogue: [github.com/architek-earth](https://github.com/architek-earth).

architek.earth · code like concrete

### 🙋 Support & legal

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.

# Actor input Schema

## `mode` (type: `string`):

One actor for match lists, match enrichment and singles rankings.

## `billingMode` (type: `string`):

Preview: 20 lifetime attempts per account, one page and at most 10 basic match rows. Paid uses the published event prices. No automatic paid fallback.

## `maxItems` (type: `integer`):

Data rows per run; unchanged incremental rows and free status rows do not consume this limit.

## `dayOffsets` (type: `array`):

Match lists only. Integers -7 to +7; resumed collections keep their original date anchor. Choose offsets OR dateFrom/dateTo.

## `dateFrom` (type: `string`):

Optional YYYY-MM-DD, within seven days of UTC today. Do not combine with dayOffsets.

## `dateTo` (type: `string`):

Optional YYYY-MM-DD, within seven days of UTC today. Do not combine with dayOffsets.

## `tours` (type: `array`):

Match lists only. OR matching: ATP, WTA, Challenger, ITF, Doubles. Empty means all.

## `live` (type: `boolean`):

Match lists only. Source general state must identify an in-progress match.

## `tournamentIds` (type: `array`):

Match lists only; source tournament IDs, not names.

## `matchIds` (type: `array`):

Detail mode; eight-character Flashscore match IDs, at most 100.

## `matchUrls` (type: `array`):

Detail mode; Flashscore tennis URLs, including URLs with a mid query parameter.

## `tour` (type: `string`):

ATP or WTA singles rankings. Doubles deferred.

## `topN` (type: `integer`):

Maximum 100 per tour in v1.

## `statistics` (type: `boolean`):

Requested detail section. Missing source data is reported explicitly; failed retrieval prevents a paid detail row.

## `pointByPoint` (type: `boolean`):

Requested detail section. Missing source data is reported explicitly; failed retrieval prevents a paid detail row.

## `odds` (type: `boolean`):

Requested detail section. Missing source data is reported explicitly; failed retrieval prevents a paid detail row.

## `h2h` (type: `boolean`):

Requested detail section. Missing source data is reported explicitly; failed retrieval prevents a paid detail row.

## `incrementalMode` (type: `boolean`):

Retains a baseline under stateKey across completed runs. No schedules are created.

## `emitChanges` (type: `boolean`):

Requires incrementalMode. Changed rows are additional paid results. Off preserves new-ID-only behaviour.

## `stateKey` (type: `string`):

Required for incrementalMode or continuation. Use a unique key per set of filters. Letters, digits, hyphens and underscores; maximum 80.

## `continuePreviousRun` (type: `boolean`):

Resume an incomplete checkpoint in a fresh run with the same stateKey and inputs. Already delivered rows are skipped.

## `proxyConfiguration` (type: `object`):

No proxy by default. Optional buyer proxy configuration; proxy use has not been included in the measured default costs.

## `feedback` (type: `string`):

Optional feedback logged with this run. Does not change collection or output. Do not include secrets.

## Actor input object example

```json
{
  "mode": "matches",
  "billingMode": "paid",
  "maxItems": 3,
  "dayOffsets": [
    0
  ],
  "live": false,
  "tour": "ATP",
  "topN": 100,
  "statistics": true,
  "pointByPoint": true,
  "odds": true,
  "h2h": true,
  "incrementalMode": false,
  "emitChanges": false,
  "continuePreviousRun": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "billingMode": "paid",
    "maxItems": 3,
    "dayOffsets": [
        0
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("architekture/tennis-data-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "billingMode": "paid",
    "maxItems": 3,
    "dayOffsets": [0],
}

# Run the Actor and wait for it to finish
run = client.actor("architekture/tennis-data-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "billingMode": "paid",
  "maxItems": 3,
  "dayOffsets": [
    0
  ]
}' |
apify call architekture/tennis-data-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,architekture/tennis-data-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gtRuOXj4sMPIzgYGi/builds/Awqs4agQWImczMPQO/openapi.json
