# TennisExplorer Results, Odds, Match Stats & Players Scraper (`artsiom_k/tennisexplorer-scraper`) Actor

ATP, WTA, Challenger and ITF match results with set scores and tiebreaks, bookmaker odds per match (winner, over/under, handicap, correct score; opening and closing), player profiles, and SofaScore serve/return match statistics with point-by-point.

- **URL**: https://apify.com/artsiom_k/tennisexplorer-scraper.md
- **Developed by:** [Artsiom Kunitsyn](https://apify.com/artsiom_k) (community)
- **Categories:** Other, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## TennisExplorer Results, Odds, Match Stats & Players Scraper

Tennis results, bookmaker odds and player profiles from TennisExplorer, plus **match statistics and point-by-point
from SofaScore**, for betting models, research and fantasy tools. ATP, WTA, Challenger, WTA 125, UTR and ITF,
singles and doubles, day by day back to the 1990s.

### What you get

#### `entityType: "matches"` (default): one row per finished match

| Field | Example |
|---|---|
| `date`, `time_cet`, `tour` | `2025-06-20`, `17:05`, `atp-single` |
| `tournament`, `tournament_level`, `tournament_country` | `Halle`, `tour`, `DE` |
| `winner`, `winner_seed`, `loser`, `loser_seed` | `Khachanov K.`, `8`, `Etcheverry T.` |
| `score`, `winner_sets`, `loser_sets` | `7-6(2), 6-3`, `2`, `0` |
| `sets` (per set: games and tiebreak points) | `[{"winner_games": 7, "loser_games": 6, "tiebreak_loser": 2}, ...]` |
| `winner_odds_avg`, `loser_odds_avg` (average closing odds) | `1.36`, `3.12` |

Turn on **Include match details** to add the round, the surface, and both players' ranking at the time, birthdate,
height, weight and playing hand.

#### `entityType: "odds"`: one row per match, bookmaker and market

Markets: **match winner**, **over/under** (sets and games), **Asian handicap** and **correct score**, from up to ~20
bookmakers (bet365, Pinnacle, Betfair, 1xBet, Unibet, William Hill and more). Each row has the **opening odds and their
time**, the **closing odds**, and when the line last moved.

| Field | Example |
|---|---|
| `player1`, `player2` (winner first), `market`, `line` | `Khachanov K.`, `Etcheverry T.`, `asian_handicap`, `-1.5` |
| `bookmaker` | `1xBet` |
| `selection1_opening_odds` → `selection1_odds` | `1.35` → `1.38` |
| `selection2_opening_odds` → `selection2_odds` | `3.27` → `3.12` |
| `selection1_opening_time`, `selection1_last_change` | `2025-06-19T19:00`, `2025-06-20T16:21` |

#### `entityType: "players"`: player profiles from the ATP/WTA rankings

Name, country, birthdate, height, weight, playing hand, current and highest singles and doubles ranking, ranking points.

#### `entityType: "matchStats"`: match statistics and point-by-point (from SofaScore)

One row per finished match, with both players' serve and return statistics for the whole match, the same
statistics **per set** (`statistics_by_set`), and the **point-by-point** record (`point_by_point`).

| Field | Example |
|---|---|
| `date`, `start_time_utc`, `tournament`, `category`, `round`, `surface` | `2026-10-10`, `2026-10-10T12:45+00:00`, `Shanghai`, `ATP`, `Round of 64`, `Hardcourt outdoor` |
| `player1`, `player2`, `player1_country`, `player1_seed` | `Sebastián Báez`, `Valentin Vacherot`, `ARG`, `null` (unseeded) |
| `winner`, `score`, `sets`, `status` | `Valentin Vacherot`, `4-6, 6-7(5)`, per set games and tiebreak points, `Ended` / `Retired` / `Walkover` |
| `player1_aces`, `player1_double_faults` | `5`, `0` |
| `player1_first_serve_in` / `_total` | `52` / `68` |
| `player1_first_serve_points_won` / `_total`, `player1_second_serve_points_won` / `_total` | `34` / `52`, `11` / `16` |
| `player1_break_points_saved` / `_total`, `player1_break_points_converted` | `1` / `3`, `1` |
| `player1_service_games_played`, `player1_service_games_won`, `player1_return_games_played` | `11`, `9`, `11` |
| `player1_first_serve_return_points_won` / `_total`, `player1_second_serve_return_points_won` / `_total` | `7` / `42`, `8` / `22` |
| `player1_points_total`, `player1_service_points_won`, `player1_return_points_won`, `player1_games_won` | `60`, `45`, `15`, `10` |
| `player1_max_points_in_row`, `player1_max_games_in_row`, `player1_tiebreaks_won` | `6`, `2`, `0` |

Every `player1_` column has a `player2_` twin. `point_by_point` has one entry per game: set, game, who served,
who won it, whether it was a **break** or a **tiebreak**, the games score after it, and the running point score with
each point's winner and **ace / double fault** flags.

Two ways to choose matches:

- **By day** (the default: yesterday and today), from 2026-01-01 on. SofaScore's robots.txt disallows its
  date pages for earlier seasons, so a date range before 2026 returns nothing.
- **By player**: put SofaScore player URLs or ids in **SofaScore players** (e.g.
  `https://www.sofascore.com/tennis/player/sinner-jannik/206570`) for their full match history, back to about 2016.
  Add dates to narrow it down.

Statistics and point-by-point exist for ATP, WTA (Grand Slams included), WTA 125, Challenger and most UTR matches.
ITF matches come with the score only.

### Input

| Field | Default | Used by | What it does |
|---|---|---|---|
| `entityType` | `matches` | | `matches`, `odds`, `players` or `matchStats`. |
| `tours` | ATP and WTA singles | all | ATP/WTA singles and doubles. Each tour includes its Challenger, WTA 125, UTR and ITF events. |
| `dateFrom`, `dateTo` | yesterday and today | matches, odds, matchStats | Day range. `matchStats` lists days from 2026-01-01 on. |
| `levels` | all | matches, odds, matchStats | `tour`, `challenger`, `itf`, `utr`, `team`. |
| `tournaments` | all | matches, odds, matchStats | Keep tournaments whose name contains one of these words. |
| `players` | all | matches, odds | TennisExplorer player slugs or URLs. |
| `includeMatchDetails` | off | matches | Round, surface and both players' rank, birthdate, height, weight and hand (one extra page per match). |
| `markets` | winner, over/under, handicap | odds | Which bookmaker markets to return; add correct score if needed. |
| `sofascorePlayers` | none | matchStats | SofaScore player URLs or ids: their match history instead of a day range. |
| `includePointByPoint` | on | matchStats | Add the point-by-point record. Off = statistics only. |
| `maxItems` | `50` | all | Row cap. Clear it for everything in range. |
| `mode` | `auto` | matches, matchStats | Daily schedules with **Max results** cleared return only matches not returned before. |
| `concurrency` | `10` | all | Pages (or SofaScore requests) fetched in parallel. |
| `impersonate` | `chrome` | all | Browser fingerprint. Change only if requests start failing. |
| `proxyConfiguration` | off | matches, odds, players | TennisExplorer needs no proxy. |
| `sofascoreProxyConfiguration` | Apify Proxy (datacenter) | matchStats | SofaScore blocks most IPs; the Actor finds proxy IPs that work and reuses them. Proxy traffic is included in the price. |

### How to use it

- **Yesterday's and today's results** (the default): just run it.
- **A whole season**: `dateFrom: 2024-01-01`, `dateTo: 2024-12-31`, clear **Max results**.
- **Only the main tours**: Levels = ATP / WTA tour. **Only Grand Slams**: Tournament names = `Wimbledon`,
  `Roland Garros`, `US Open`, `Australian Open`.
- **One player's matches**: Players = `sinner-8b8e8` (the slug from their TennisExplorer URL).
- **Closing and opening odds for backtesting**: `entityType: "odds"`, pick the markets you need.
- **Serve/return stats for yesterday's matches**: `entityType: "matchStats"`, Levels = ATP / WTA tour and Challenger.
- **A player's career stats**: `entityType: "matchStats"`, SofaScore players = their SofaScore URL, clear
  **Max results**. Turn off **Include point-by-point** if you only need the statistics.
- **Daily schedule**: run every day with the default dates and **Max results** cleared; `mode: auto` returns only matches
  not returned before (for `matches` and `matchStats`).

### Good to know

- Times are Central European time, as on the site.
- Results pages list finished matches; a match still being played appears once it's over.
- Average odds exist for most main-tour matches; small ITF matches often have none.
- Bookmaker coverage varies by match: big matches have ~20 books, small ones a handful or none.
- The odds rows carry opening and closing odds, not every move in between.
- `matches`, `odds` and `players` need no proxy. `matchStats` goes through Apify Proxy, because SofaScore blocks
  most IPs: the Actor finds proxy IPs that get through and keeps reusing them. Proxy traffic is included in the price.
- In `matchStats`, player 1 and player 2 follow SofaScore's order (home/away), not winner first; `winner` and
  `winner_code` say who won.

### Related sports data scrapers

- [Smarkets Betting Exchange Scraper](https://apify.com/artsiom_k/smarkets-scraper) — betting exchange back and lay odds, matched volume, results and traded-price history.
- [Action Network Scraper](https://apify.com/artsiom_k/actionnetwork-scraper) — US sportsbook odds, public betting splits, line movement, props, injuries and futures.
- [Sports Odds History Scraper](https://apify.com/artsiom_k/sports-odds-history-scraper) — opening and closing lines per sportsbook since 2021 and ATS results since 2006.
- [Kalshi & Polymarket Scraper](https://apify.com/artsiom_k/prediction-markets-scraper) — prediction market odds, settled results and price history for sports, politics, economics and crypto.
- [SofaScore Scraper](https://apify.com/artsiom_k/sofascore-scraper) — results, team statistics, lineups with player ratings, incidents and odds for football, basketball, hockey, tennis and 17 more sports.
- [Flashscore Scraper](https://apify.com/artsiom_k/flashscore-scraper) — results, statistics, lineups, incidents and opening/closing odds from ~20 bookmakers for 17 sports.

# Actor input Schema

## `entityType` (type: `string`):

"matches": one row per finished match, with set scores, tiebreaks and average closing odds. "odds": one row per match, bookmaker and market (match winner, over/under, Asian handicap, correct score) with opening and closing odds. "players": player profiles walked from the ATP/WTA singles rankings. "matchStats": one row per finished match from SofaScore with serve and return statistics (aces, double faults, first-serve %, break points, points and games won; whole match and per set) and point-by-point.

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

Which tours. Each includes its Challenger/125, UTR and ITF events; filter those with Levels. Used by every type (players: singles only).

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

matches/odds/matchStats: first day (YYYY-MM-DD). Leave both dates empty for yesterday and today. Results and odds go back to the 1990s. matchStats lists days from 2026-01-01 on; for older matches use SofaScore players (then the dates only narrow their history).

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

matches/odds/matchStats: last day. Defaults to today when only dateFrom is set.

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

matches/odds/matchStats: tournament levels to keep. Leave empty for all. matchStats: ITF matches have no statistics on SofaScore (score only).

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

matches/odds/matchStats: keep only tournaments whose name contains one of these (e.g. Wimbledon).

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

matches/odds: keep only matches with one of these players, by TennisExplorer slug or player URL (e.g. sinner-8b8e8). For matchStats use SofaScore players.

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

matches: add round, surface and both players' rank, birthdate, height, weight and hand (one extra page per match, slower).

## `markets` (type: `array`):

odds: which markets to return.

## `sofascorePlayers` (type: `array`):

matchStats: return these players' match history instead of listing by day, back to about 2016. SofaScore player URL or id, e.g. https://www.sofascore.com/tennis/player/sinner-jannik/206570 or 206570. Dates, tours, levels and tournament names still filter.

## `includePointByPoint` (type: `boolean`):

matchStats: add every game's server, winner, break flag and running point score with aces and double faults. Turn off for statistics only (one request less per match).

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

Stop after this many rows. Defaults to 50 for a quick preview; clear it (null) for everything in range.

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

matches and matchStats. "auto": with maxItems cleared, later runs return only matches not returned before (handy for a daily schedule over overlapping dates); a capped run or a SofaScore player history always returns rows. "full": everything. "incremental": only new/changed.

## `concurrency` (type: `integer`):

Pages (or SofaScore requests) fetched in parallel.

## `impersonate` (type: `string`):

curl_cffi browser fingerprint; defaults to "chrome".

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

TennisExplorer (matches, odds, players): not needed; the site answered every request from Apify's infrastructure without a proxy.

## `sofascoreProxyConfiguration` (type: `object`):

matchStats: SofaScore blocks most IPs, so the Actor finds proxy IPs that work and keeps reusing them. Apify's datacenter proxy (the default) is enough; Residential is tried first if you pick it. Proxy traffic is included in the price.

## Actor input object example

```json
{
  "entityType": "matches",
  "tours": [
    "atp-single",
    "wta-single"
  ],
  "includeMatchDetails": false,
  "markets": [
    "match_winner",
    "over_under",
    "asian_handicap"
  ],
  "includePointByPoint": true,
  "maxItems": 50,
  "mode": "auto",
  "concurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "sofascoreProxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "sofascoreProxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("artsiom_k/tennisexplorer-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 = { "sofascoreProxyConfiguration": { "useApifyProxy": True } }

# Run the Actor and wait for it to finish
run = client.actor("artsiom_k/tennisexplorer-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 '{
  "sofascoreProxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call artsiom_k/tennisexplorer-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,artsiom_k/tennisexplorer-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/YQbuRj9MREZTAUsA7/builds/2CKnd3heRkxWcVt5v/openapi.json
