# Tennis Results & Odds Scraper - ATP, WTA, Challenger, ITF (`dami_studio/tennis-matches-scraper`) Actor

Every tennis match TennisExplorer lists for the days you pick: tour, level, tournament, surface, round, both players, set scores with tiebreaks, winner, retirements and average odds. Upcoming matches come with odds too. Filter by tour, level, tournament, player or surface.

- **URL**: https://apify.com/dami\_studio/tennis-matches-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Sports, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.95 / 1,000 match returneds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Tennis Results & Odds Scraper

Pick a day, or a run of days, and get every tennis match TennisExplorer lists for it, one row per match: date and start time, tour, singles or doubles, level, tournament, surface, round, both players with their seeds, the score set by set with tiebreaks, the winner, how it ended, and the average odds. Days that haven't happened yet give the schedule, with odds and the head-to-head count.

It reads TennisExplorer's public pages. There's no key to get and nothing to log in to.

For scale: TennisExplorer lists 170 finished matches for Saturday 12 September 2026, from the US Open women's final down to ITF events in Egypt and Tunisia.

### What it doesn't do

- **No live scores.** A match being played when the run reads its day is left out, and the status message counts them. Run it again once they're over.
- **No point-by-point or serve stats.** The odds are the match-winner market only.
- **Men's ITF matches come without their tournament.** TennisExplorer files all of them under one "Futures" heading, so their surface and round stay empty unless you turn on Match details, which fills both in.
- **The schedule runs a day or two ahead, not more.** On 13 September 2026 TennisExplorer had 183 matches for the next day, about 45 for the day after and none further out.
- **Retirements are read from the score.** TennisExplorer puts the winner first but doesn't write "retired" in its results. So a finished match whose last set stopped short is marked `retired`, and one with no games at all `walkover`. A player who pulls out between two sets of a best-of-five match reads as an ordinary win.

### Input

**Last days.** The last few full days of results, ending yesterday. Set it to `1` on a daily schedule and each run brings in yesterday's matches.

**Upcoming days.** Today's matches plus the schedule after it. `1` is today only, `2` adds tomorrow.

**From date, To date.** A fixed range instead, written like `2026-09-01`, up to 366 days per run. Leave To date empty and the run goes up to today.

**Match types.** Men's singles, men's doubles, women's singles, women's doubles. Empty means all four.

**Levels.** ATP and WTA tour events (Grand Slams included), Challenger, ITF, other lower-level events, and exhibitions. Challenger and ITF are known from the tournament's name, the rest from TennisExplorer's tour calendar.

**Tournaments, Players.** Part of a name as TennisExplorer writes it (`US Open`, `Sinner`), or a link to the tournament's or the player's TennisExplorer page. For players either side counts, doubles partners included. Accents and capitals don't matter.

**Surfaces.** Hard, clay, grass, indoors.

**Average odds.** On unless you turn it off.

**Round.** On unless you turn it off. It reads each tournament's page once, and the qualifying page when a match isn't in the main draw. Turning it off makes long ranges finish sooner.

**Match details.** Off unless you turn it on. It opens each match's own page and adds the players' full names, rankings, birth dates, height and playing hand, plus every bookmaker's price with its opening price. That's one more page per match, so a run with details stops at 2,000 matches.

**Player countries.** Off unless you turn it on. One page per player, read once per run.

**Maximum matches.** 1,000 unless you change it, up to 25,000 per run.

```json
{
  "lastDays": 7,
  "matchTypes": ["atp-single"],
  "levels": ["challenger"],
  "surfaces": ["clay"],
  "maxMatches": 2000
}
```

### Output

One row per match, day by day, in the order TennisExplorer lists them.

| Field | Example | Notes |
|---|---|---|
| `matchId` | `3321141` | TennisExplorer's id for the match |
| `date` | `2026-09-12` | the day TennisExplorer puts the match on, in Prague time |
| `time` | `22:15` | start time in Prague time, empty when TennisExplorer has none |
| `startTimeUtc` | `2026-09-12T20:15:00Z` | the same moment in UTC |
| `status` | `finished` | or `scheduled` |
| `outcome` | `completed` | `retired`, `walkover`, or empty on a match not played yet |
| `tour` | `WTA` | `ATP` for men, `WTA` for women |
| `matchType` | `singles` | or `doubles` |
| `level` | `tour` | `challenger`, `itf`, `other-lower`, `exhibition`, or empty when unknown |
| `tournament` | `US Open` | as TennisExplorer names it |
| `tournamentCountryCode` | `US` | where the tournament is played |
| `surface` | `hard` | `clay`, `grass` or `indoors` |
| `round` | `final` | as TennisExplorer writes it, such as `1. round` or `Qualification - 2. round` |
| `player1`, `player2` | `Rybakina E.`, `Sabalenka A.` | in TennisExplorer's order, winner first on a finished match. A doubles team reads `Latinovic S. / Poljicak M.` |
| `player1Seed`, `player2Seed` | `2`, `1` | empty when unseeded |
| `winner` | `Rybakina E.` | |
| `score` | `6-4, 5-7, 6-2` | from player 1's side. A tiebreak shows the loser's points: `7-6(3)` |
| `player1Sets`, `player2Sets` | `2`, `1` | TennisExplorer's sets column. On a retirement it counts the unfinished set for the winner |
| `sets` | `[{"player1":6,"player2":4,"tiebreak":null}, …]` | games per set |
| `player1Odds`, `player2Odds` | `2.26`, `1.64` | the bookmakers' average. For a finished match, their last prices before it started |
| `player1H2H`, `player2H2H` | `5`, `0` | head-to-head wins, on scheduled matches |
| `player1Url`, `player2Url`, `tournamentUrl`, `url` | | the TennisExplorer pages |

With Match details on, each row also carries `player1FullName`, `player1Rank`, `player1BirthDate`, `player1HeightCm` and `player1Plays`, the same for player 2, `bookmakerOdds` (each bookmaker's price for both players and its opening price) and `detailsRead`. On a doubles match the full names cover both partners, and rank, birth date, height and hand stay empty, since one value can't describe two players. With Player countries on: `player1Country` and `player2Country`.

The run also leaves `RUN_REPORT` in its key-value store: what each day listed and returned, what was left out and why, and why the run stopped.

### What you pay

Each match in your dataset is one charge. Nothing else costs anything: not a day with no matches, not a day TennisExplorer didn't answer, and not a match your filters left out or one still being played. With Match details on, a match whose own page couldn't be read comes back without the details and isn't charged. If you set a maximum charge for the run, it stops when that's reached, and every row you get has been paid for. The price is on the Pricing tab.

### Limits

- 366 days and 25,000 matches per run, 2,000 with Match details on.
- The actor reads one page a second. A day is one page. Round adds one page per tournament, Match details one page per match, Player countries one page per player.
- The date is the one TennisExplorer gives the match, in Prague time. A match that starts at 00:30 in Prague sits on that new day, even when it was still the evening before where it was played.
- When TennisExplorer stops answering for a while, the actor waits two minutes and carries on. If a day still can't be read, the status names it and nothing is charged for it.

### Where the data comes from

TennisExplorer, tennisexplorer.com. What you do with the data is up to you, and TennisExplorer's own terms of use apply to its content.

### Questions

**How do I get yesterday's results every morning?** Schedule the actor with Last days set to 1.

**How do I get tomorrow's matches with odds?** Set Upcoming days to 2 for today's and tomorrow's schedule. Odds keep moving until a match starts, so run it close to when you need them.

**Why is the round empty on some matches?** Round is off, or it's a men's ITF match, or a UTR event (they come without rounds), or the tournament's pages didn't list that match. Match details fill in the round for any match that has one.

# Actor input Schema

## `lastDays` (type: `integer`):

The last N full days of results, ending yesterday. 1 is yesterday only, which suits a daily schedule. Used when From date is empty.

## `upcomingDays` (type: `integer`):

Today's matches plus the schedule after it: 1 is today only, 2 adds tomorrow. TennisExplorer usually lists the next day or two, with odds. Used when From date is empty.

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

The first day, as YYYY-MM-DD, for a fixed range instead of Last days. Past days go back as far as TennisExplorer has results.

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

The last day. Leave it empty to stop at today. One run covers up to 366 days.

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

Leave it empty for all four. Men's matches come under ATP and women's under WTA, Challenger and ITF events included.

## `levels` (type: `array`):

Leave it empty for every level. Challenger and ITF are known from the tournament's name, the rest from TennisExplorer's tour calendar.

## `tournaments` (type: `array`):

Part of a tournament's name as TennisExplorer writes it, such as US Open or Genoa, or a link to the tournament's TennisExplorer page. A match is kept when its tournament matches any of them.

## `players` (type: `array`):

A surname as TennisExplorer writes it, such as Sinner or Rybakina, or a link to the player's TennisExplorer page. A match is kept when either side matches, doubles partners included.

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

Leave it empty for any surface. The surface comes from TennisExplorer's tour calendar. Men's ITF matches are listed without their tournament, so a surface filter leaves them out.

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

The average of the bookmakers' prices for each player, as the list shows it. For a finished match these are the last prices before it started.

## `addRounds` (type: `boolean`):

Reads each tournament's page once to add the round: final, semifinal, 1. round and so on. Turn it off on long ranges to finish sooner.

## `includeMatchDetails` (type: `boolean`):

Opens each match's own page for the players' full names, rankings, birth dates, height and playing hand, and every bookmaker's odds with the opening prices. That's one more page per match, so a run with details stops at 2,000 matches.

## `addPlayerCountries` (type: `boolean`):

Reads each player's page once for their country. That's one more page per player, so a busy day takes several minutes longer.

## `maxMatches` (type: `integer`):

The most matches returned in one run. Each match returned is one charge.

## Actor input object example

```json
{
  "lastDays": 1,
  "includeOdds": true,
  "addRounds": true,
  "includeMatchDetails": false,
  "addPlayerCountries": false,
  "maxMatches": 20
}
```

# Actor output Schema

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

One row per match: date and time, tour, match type, level, tournament, surface, round, both players, the score set by set, the winner, how it ended and the average odds.

## `report` (type: `string`):

What each day listed and returned, matches left out and why, pages that couldn't be read, and why the run stopped.

# 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 = {
    "lastDays": 1,
    "maxMatches": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/tennis-matches-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 = {
    "lastDays": 1,
    "maxMatches": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/tennis-matches-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 '{
  "lastDays": 1,
  "maxMatches": 20
}' |
apify call dami_studio/tennis-matches-scraper --silent --output-dataset

```

## MCP server setup

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