# Tennis Scraper - ATP, WTA, ITF Live Scores, Results, H2H (`snow_leo_data/tennis-scraper`) Actor

Tennis scraper for ATP, WTA, Challenger and ITF: one day feed returned 549 matches from 80 tournaments in a single request. Live scores, set scores, tiebreaks, surface, singles or doubles, head-to-head and odds. 15-day window, no browser, no proxy, no API key.

- **URL**: https://apify.com/snow_leo_data/tennis-scraper.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Sports, News, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.20 / 1,000 match rows

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: ATP, WTA, Challenger and ITF Live Scores, Results, H2H

Scrape tennis matches without a browser, a proxy or an API key. One request
returns a whole day of tennis: on 21 October 2026 a single day feed carried
**549 matches from 80 tournaments** (ATP, WTA, Challenger, ITF men, ITF women,
singles and doubles). The window is 15 days: seven days back, today and seven
days ahead.

Every row is one match. You pay per match row that reaches your dataset. There
is no start fee and no per-request fee.

### What one run returns

- **Who and when:** both sides (players or doubles pairs), country of each
  side, start time in UTC, status (`scheduled`, `live`, `finished`), link to the
  match.
- **Score:** sets won by each side, the score of every set in `period_scores`,
  and the tiebreak score of every set that went to one in `tiebreaks`. A
  `winner_name` column saves you the lookup.
- **Tournament, parsed for you:** `tour` (atp, wta, challenger, itf-men,
  itf-women, teams, exhibition), `gender`, `match_type` (singles or doubles),
  `event_name`, `event_country`, `prize_level` (M15, M25, W35, W100 and so on),
  `surface` (hard, clay, grass, carpet), `indoor` and `qualification`.
- **Optional extras** that you switch on: head-to-head history of both sides
  per match (`includeHeadToHead`), bookmaker odds (`includeOdds`) and the raw
  feed keys (`includeRawFields`).

Example row, shortened:

```json
{
  "event_id": "zHfvs0hR",
  "tour": "atp",
  "match_type": "singles",
  "event_name": "Almaty",
  "event_country": "Kazakhstan",
  "surface": "hard",
  "indoor": true,
  "home_name": "Bellucci M.",
  "away_name": "Etcheverry T. M.",
  "status": "finished",
  "home_score": 2,
  "away_score": 1,
  "period_scores": [{"period": "1", "home": 7, "away": 6}, {"period": "2", "home": 2, "away": 6}, {"period": "3", "home": 6, "away": 4}],
  "tiebreaks": [{"period": "1", "home": 7, "away": 5}],
  "winner_name": "Bellucci M."
}
```

### Input

| Field | What it does |
| --- | --- |
| `dayOffsets` | Days relative to today. `0` today, `-1` yesterday, `3` in three days. Allowed from -7 to +7. |
| `tours` | `atp`, `wta`, `challenger`, `itf-men`, `itf-women`. Empty means everything on the feed. |
| `matchTypes` | `singles`, `doubles`, or both. |
| `surfaces` | `hard`, `clay`, `grass`, `carpet`. |
| `matchStatuses` | `scheduled`, `live`, `finished`. |
| `tournamentContains` | Words that must appear in the tournament line, for example `Almaty` or `Wimbledon`. |
| `playerContains` | Words that must appear in either side, for example `Sinner`. Doubles match on either partner. |
| `includeHeadToHead`, `includeOdds`, `includeRawFields` | Extra data, off by default. |
| `onlyChanged` | Incremental mode: only matches that are new or changed since the previous run with the same `monitorStoreName`. |
| `compactOutput` | Drops empty columns, handy for AI agents and narrow CSV files. |
| `maxItems` | Caps rows delivered, not rows scanned. Filtered rows are never charged. |

A schedule with `onlyChanged` and `matchStatuses: ["live"]` gives you a live
score feed that pays only for matches whose score moved.

### Typical uses

- **Live score widgets and Telegram bots:** run every few minutes in
  incremental mode.
- **Betting models and analytics:** daily finished results with set scores,
  tiebreaks, surface and head-to-head history for training data.
- **Fantasy and media sites:** the day's schedule per tournament or per player.
- **AI agents:** `compactOutput` plus `maxItems` keeps answers short and cheap.

### Honest limits

- The source serves only a window of **-7 to +7 days**. Older results are not
  available here.
- Player names come as the source writes them (`Sinner J.`). Rankings, point by
  point statistics and player profiles are **not** part of this Actor.
- Odds appear only for matches the source has odds for.
- Tennis feeds are quiet outside the season: an empty run names the reason in
  the log and in the `REPORT` record instead of failing silently.

### Price

Pay per event: one match row delivered. Rows removed by your filters, rows that
are duplicates inside one run and, in incremental mode, rows already delivered
are not charged. There is no start fee. A full day of tennis, about 550 rows,
costs well under a dollar.

### Related Actors

Need football, basketball, hockey and the other 28 sports from the same source?
Use the Flashscore Scraper from the same publisher.

# Actor input Schema

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

Days relative to today: 0 is today, -1 yesterday, 3 in three days. The source serves -7..+7; days outside are dropped before any request.

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

Keep only these tours. Empty means every tour on the feed: ATP, WTA, Challenger, ITF men, ITF women, team events and exhibitions.

## `matchTypes` (type: `array`):

Singles, doubles or both (empty).

## `surfaces` (type: `array`):

Hard, clay, grass or carpet. Empty means all surfaces.

## `matchStatuses` (type: `array`):

Keep only these. Leave empty for all three.

## `tournamentContains` (type: `array`):

Keep matches whose tournament line contains any of these words, e.g. Almaty, Roland Garros, Wimbledon, United Kingdom.

## `playerContains` (type: `array`):

Keep matches where either side contains any of these words, e.g. Sinner, Swiatek. Doubles pairs match on either partner. Names look like the source writes them: Sinner J.

## `includeHeadToHead` (type: `boolean`):

Read previous meetings and recent form for every delivered match. One extra request per match, so it is off by default.

## `includeOdds` (type: `boolean`):

Add the 1/X/2, handicap, over-under and moneyline numbers that travel in the same feed. Off by default.

## `includeRawFields` (type: `boolean`):

Add every raw feed key with the constant name Flashscore's own bundle gives it. Useful for sport-specific values this Actor does not name yet.

## `compactOutput` (type: `boolean`):

Drop empty fields from every row. Smaller CSV, friendlier to AI agents.

## `onlyChanged` (type: `boolean`):

Remember what was delivered and write only new or changed matches. Made for scheduled monitoring.

## `emitUnchanged` (type: `boolean`):

With incremental mode on, still write matches that did not change, tagged UNCHANGED.

## `monitorStoreName` (type: `string`):

Named key-value store that holds the incremental memory. Change it to keep several schedules apart.

## `language` (type: `string`):

Language of tournament and participant names.

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

Upper bound on DELIVERED rows, not on rows scanned. 0 means no limit.

## `detailConcurrency` (type: `integer`):

Parallel head-to-head requests.

## Actor input object example

```json
{
  "dayOffsets": [
    "0"
  ],
  "matchStatuses": [],
  "tournamentContains": [],
  "playerContains": [],
  "includeHeadToHead": false,
  "includeOdds": false,
  "includeRawFields": false,
  "compactOutput": false,
  "onlyChanged": false,
  "emitUnchanged": false,
  "language": "en",
  "maxItems": 200,
  "detailConcurrency": 8
}
```

# Actor output Schema

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

All collected rows

# 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 = {
    "dayOffsets": [
        "0"
    ],
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/tennis-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 = {
    "dayOffsets": ["0"],
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/tennis-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 '{
  "dayOffsets": [
    "0"
  ],
  "maxItems": 200
}' |
apify call snow_leo_data/tennis-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,snow_leo_data/tennis-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/gssOHNdSEP9FAv4De/builds/SBCg40hH9BUleoSHY/openapi.json
