# TennisExplorer Scraper — ATP/WTA Results, Rankings, Players (`oswaldocarabano/tennisexplorer-scraper`) Actor

Extract ATP, WTA, Challenger and ITF match results, upcoming schedules, player profiles and rankings from TennisExplorer, with derived match status and a pinned UTC day boundary.

- **URL**: https://apify.com/oswaldocarabano/tennisexplorer-scraper.md
- **Developed by:** [Oswaldo Carabano](https://apify.com/oswaldocarabano) (community)
- **Categories:** Sports, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## TennisExplorer Results, Schedule, Players & Rankings Scraper

Extract ATP, WTA, Challenger and ITF tennis data from TennisExplorer: finished match
results with set-by-set scores, upcoming schedules, player profiles with career
history, and ranking tables going back to 1995.

> 🚧 **Pre-release.** Not published yet.

### What makes this one different

**1. Match status you can actually filter on.** TennisExplorer does not label
retirements or walkovers anywhere — an unfinished match just shows a scoreline that
never closes. This Actor derives a `status` field (`completed`, `retired`,
`walkover`) and it works for best-of-3 and best-of-5 alike, without needing to know
which is which. Validated against 2,364 matches across nine tournaments with
different formats, including Davis Cup, which mixes both on the same page.
About **4.6 %** of matches are affected.

`retired` means exactly *the match started and did not finish* — retirement, injury,
default, or an abandoned dead rubber. The source does not distinguish them, so
neither do we.

**2. A day boundary that does not move.** The site decides which matches belong to
which day from a timezone cookie. The same date returned **227 matches in one
timezone and 269 in another** during testing. Every request here is pinned to
**UTC**, so two runs of the same date return the same set of matches. Every time
field is UTC and named `start_time_utc`.

**3. Full names in doubles.** The site truncates the visible team name
("Roger-Vas"). This Actor reads the full name from the underlying attribute. That is
**17.4 %** of all rows.

**4. History back to 1995.** The same input works for any past date. A full season is
around 148,000 matches — that is measured, not estimated.

### Output

One entity type per run, so the file you download has one clean shape:

| `entityType` | Rows | Typical size |
|---|---|---|
| `results` | Finished matches for a date range | 150–600 per day |
| `schedule` | Upcoming and in-progress matches | ~200 per day |
| `players` | Profiles, optionally with match history | 1 per player |
| `rankings` | ATP or WTA tables, singles / doubles / race | 50 per page |

A single day of results costs one request. A full season is 365.

### Pricing

Two tiers, so bulk history is actually affordable:

- **$0.0015** per result for the first 10,000 rows of a run
- **$0.0003** per result after that
- **$0.0024** surcharge per match when `includeMatchDetail` is on
- **Free** to start a run
- **Failed requests are never charged.** They go to the `ERRORS` record instead.

A week of results is about **$4**. A full season of ~148,000 matches is about **$56**.

### Betting odds are off by default

`includeOdds` is off on purpose. The odds on this site come from bookmakers, not from
the site itself, and redistributing them sits in a very different position from match
scores. Turn it on only if you have your own basis for using them.

### Data notes

- All times and dates are **UTC**.
- Players under 18 are returned with `birth_year` instead of a full `birthdate`, and
  without a photo URL.
- For a match that did not finish, the site's result column is a **won/lost flag**,
  not a set count — it reads `1-0` no matter how many sets were played. `home_sets`
  and `away_sets` are recounted from the scoreline in that case, so they are never
  the flag. `sets` always carries the raw scoreline.
- `maxConcurrency` is capped to what fits in the memory the run was given. Give a
  backfill 1024 MB or more if you want it to go fast.

### Not affiliated

This Actor is an independent tool. It is not affiliated with, endorsed by, or
connected to TennisExplorer or its operator.

Privacy requests: `privacy@actorstack.dev`

# Actor input Schema

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

One entity type per run, so the dataset you download has one clean shape. Match results, upcoming matches, player profiles and rankings have genuinely different fields; run the Actor once per type if you need more than one.

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

First day to collect, as YYYY-MM-DD. Days are cut at 00:00 UTC, always — the site's own day boundary moves with the visitor's timezone, and this Actor pins it so two runs of the same date return the same matches. History goes back to 1995. Accepts a date, or a relative one: today, yesterday, tomorrow, -7d, +3d. Leave empty for today.

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

Last day to collect, as YYYY-MM-DD, inclusive. Accepts a date or a relative one (today, -1d). Leave empty to use the From date. One request covers one whole day and typically returns 150-600 matches.

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

Which tours to include. Leave all selected to get everything in a single request per day, which is the cheapest option.

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

Home/Away odds shown in the results table. Off by default: odds are licensed from bookmakers and are the part of this site with the weakest reuse position. Match scores are facts; odds are not. Turn this on only if you have your own basis for using them.

## `includeMatchDetail` (type: `boolean`):

Adds round, surface, full player names, both rankings and head-to-head, at the price of one extra request per match. A day of results is ~200-600 matches, so leave this off unless you need it.

## `rankingTour` (type: `string`):

Which ranking table to read.

## `rankingType` (type: `string`):

Singles, doubles, or the season race. Race tables only exist for the current season.

## `rankingDate` (type: `string`):

Historical ranking week as YYYY-MM-DD. Must be one of the publication dates the site offers; leave empty for the latest.

## `playerUrls` (type: `array`):

TennisExplorer player pages to read, for example https://www.tennisexplorer.com/player/fritz-f1aa7/. Leave empty to take the players from the ranking selected above.

## `playerMatchHistoryYears` (type: `integer`):

How many past seasons of match history to read per player. 0 reads the profile only. Each extra year is one extra request.

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

Hard cap on the number of results this run will deliver. You are only charged for results actually delivered. Raise it for bulk history runs — a full season is ~100,000 matches and the whole 1995-2026 archive is ~2.4 million.

## `maxConcurrency` (type: `integer`):

Parallel requests. 8 is plenty for day-to-day use; raise it for large historical backfills. Requests are spread across a pool of sticky proxy sessions. Capped at 16 because that is the highest level actually measured against the site, and also capped to what fits in the memory you gave the run — a 512 MB run tops out around 10.

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

Optional. This Actor already routes through its own residential proxy pool, so you do not need to configure anything — leave this empty and it just works. Set it only if you would rather use your own Apify proxy, or none at all.

## Actor input object example

```json
{
  "entityType": "results",
  "dateFrom": "yesterday",
  "dateTo": "today",
  "tours": [
    "atp-single",
    "atp-double",
    "wta-single",
    "wta-double"
  ],
  "includeOdds": false,
  "includeMatchDetail": false,
  "rankingTour": "atp-men",
  "rankingType": "singles",
  "playerUrls": [],
  "playerMatchHistoryYears": 0,
  "maxItems": 10000,
  "maxConcurrency": 8,
  "proxyConfiguration": {}
}
```

# Actor output Schema

## `dataset` (type: `string`):

Everything this run collected: matches, players or ranking rows, depending on what you asked for.

## `scores` (type: `string`):

The same matches, trimmed to the scoreline, the per-set breakdown and the derived status (completed, retired or walkover).

## `odds` (type: `string`):

The same matches, trimmed to the Home/Away odds. Empty unless you turned odds on.

## `errors` (type: `string`):

Anything that could not be fetched. Failed requests are never charged.

# 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 = {
    "dateFrom": "yesterday",
    "dateTo": "today",
    "playerUrls": [],
    "proxyConfiguration": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("oswaldocarabano/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 = {
    "dateFrom": "yesterday",
    "dateTo": "today",
    "playerUrls": [],
    "proxyConfiguration": {},
}

# Run the Actor and wait for it to finish
run = client.actor("oswaldocarabano/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 '{
  "dateFrom": "yesterday",
  "dateTo": "today",
  "playerUrls": [],
  "proxyConfiguration": {}
}' |
apify call oswaldocarabano/tennisexplorer-scraper --silent --output-dataset

```

## MCP server setup

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