# Tennis Scraper: ATP & WTA Rankings, Matches, Stats, H2H (`siftwright_data/tennis-data-scraper`) Actor

Tennis scraper and ATP WTA rankings API: official rankings with Elo, live scores and results, tournaments, player profiles, match-by-match serve/return stats and head-to-head. Search by player name.

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

## Pricing

$2.50 / 1,000 result saveds

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 rankings API, live scores, match stats and head-to-head

Get **ATP and WTA tennis data** in one Actor: official **rankings** with **Elo ratings**, **live scores and match results**, the **tournament calendar**, **player profiles**, **match-by-match serve and return statistics** and **head-to-head records**. Type player names; you don't need to look up IDs first.

The data comes from public ESPN tennis feeds (rankings, scores, tournaments) and Tennis Abstract (Elo ratings, player bios, per-match statistics). The Actor uses no browser and needs no login or API key, so a typical run finishes in a few seconds.

> This Actor is an independent tool. It is not affiliated with, endorsed by or sponsored by the ATP, the WTA, ESPN or Tennis Abstract. Their names are used only to describe where the public data comes from.

### What this tennis scraper gives you

| Mode | What you get | Rows |
|---|---|---|
| **Rankings** | Current ATP and WTA top 150: rank, previous rank, movement, points, country, age, plus overall, hard, clay and grass **Elo**. Or switch to the full **Elo rating list**. | 1 per player |
| **Matches & live scores** | Live, finished and scheduled matches for any day or date range: tournament, round, court, players, set-by-set score with tie-breaks, winner, retirements and walkovers. Singles and doubles. | 1 per match |
| **Tournaments** | ATP and WTA calendar for a date range: dates, location, Grand Slam flag, match count, live matches, champions and previous winners. | 1 per tournament |
| **Player profiles & stats** | Country, age, height, plays, backhand, current and peak rank, Elo and surface Elo, last-52-weeks and season record, win % by surface, ace %, first-serve %, serve and return points won, break points saved, recent form, last match. | 1 per player |
| **Player match stats** | Each match with opponent, rank, surface, level, round, score, duration and **full tennis match stats** for both players: aces, double faults, first serve in/won, second serve won, break points saved/faced. Filter by season, surface or last N matches. | 1 per match |
| **Head-to-head (H2H)** | Win-loss record, leader, record by surface, last meeting, plus every meeting with full match stats. | 1 summary + 1 per meeting |

### Why use it

- **One tennis API instead of six tools**: rankings, scores, tournaments, players, match stats and H2H share one dataset format.
- **Names, not IDs**: enter `Sinner`, `Aryna Sabalenka` or `Sinner vs Alcaraz`. Other tools make you find a numeric ID first.
- **Elo ratings built in**: every official ranking row can carry overall and surface Elo, which is useful for betting models and predictions.
- **Deep match statistics**: serve and return numbers for both players in every tour-level match the source covers. ATP player files cover the whole career.
- **Low cost and fast**: plain HTTP, no browser, 512 MB of memory is enough.

### Use cases

- Tennis betting and prediction models (Elo, serve/return form, surface splits, H2H)
- Fantasy tennis, tipping and pick'em apps
- Live score widgets, bots and newsletters
- Sports journalism and data analysis
- Training data for AI agents and dashboards

### Input examples

ATP and WTA rankings with Elo:

```json
{ "mode": "rankings", "tours": ["atp", "wta"] }
```

Today's live tennis scores only:

```json
{ "mode": "matches", "date": "today", "matchStatus": ["live"] }
```

All WTA results from a date range, singles and doubles:

```json
{ "mode": "matches", "tours": ["wta"], "date": "2026-09-01", "endDate": "2026-09-07", "matchTypes": ["singles", "doubles"] }
```

Player profiles:

```json
{ "mode": "players", "players": ["Jannik Sinner", "Carlos Alcaraz", "Aryna Sabalenka"] }
```

Last 20 matches with full stats:

```json
{ "mode": "playerMatches", "players": ["Alcaraz"], "lastNMatches": 20 }
```

Head-to-head:

```json
{ "mode": "headToHead", "players": ["Sinner vs Alcaraz", "Sabalenka vs Swiatek"] }
```

### Input fields

| Field | Used in | Description |
|---|---|---|
| `mode` | all | `rankings`, `matches`, `tournaments`, `players`, `playerMatches` or `headToHead` |
| `tours` | all | `atp`, `wta` or both (default both) |
| `players` | players, playerMatches, headToHead, matches | Names, or `A vs B` pairs for head-to-head. In matches mode, filters to these players. |
| `rankingType` | rankings | `official` (default) or `elo` |
| `includeElo` | rankings | Add Elo columns to official rankings (default true) |
| `date`, `endDate` | matches, tournaments | `YYYY-MM-DD`, `today`, `yesterday` or `tomorrow` |
| `matchStatus` | matches | Any of `live`, `finished`, `scheduled` |
| `matchTypes` | matches | `singles` (default) and/or `doubles` |
| `wholeTournament` | matches | Return all matches of the tournaments running on the date |
| `year`, `surface`, `lastNMatches` | playerMatches, headToHead | Filters for match lists |
| `maxResults` | all | Stop after this many rows (default 1000) |

### Output examples

Ranking row:

```json
{ "type": "ranking", "tour": "atp", "rankingDate": "2026-10-15", "rank": 1, "previousRank": 1, "movement": 0, "points": 10950,
  "player": "Jannik Sinner", "country": "Italy", "countryCode": "ITA", "age": 25, "elo": 2296.9, "eloRank": 1, "hardElo": 2234.3, "clayElo": 2186.8, "grassElo": 2100.5 }
```

Match row:

```json
{ "type": "match", "tour": "atp", "tournamentName": "BNP Paribas Fortis European Open", "category": "mens-singles", "round": "Qualifying 1st Round",
  "status": "finished", "player1": "Joel Josef Schwaerzler", "player2": "Federico Cina", "winner": "Federico Cina", "score": "6-3 7-6(4)",
  "sets": [{ "player1": 3, "player2": 6 }, { "player1": 6, "player2": 7, "tiebreak": { "player1": 4, "player2": 7 } }] }
```

Player match stats row (shortened):

```json
{ "type": "playerMatch", "player": "Carlos Alcaraz", "date": "2026-10-07", "tournament": "Shanghai Masters", "surface": "Hard", "level": "Masters 1000",
  "round": "R32", "result": "L", "opponent": "Zizou Bergs", "opponentRank": 39, "score": "7-5 7-6(4)", "durationMinutes": 122,
  "stats": { "aces": 5, "doubleFaults": 2, "firstServeInPct": 67.1, "firstServeWonPct": 66, "secondServeWonPct": 53.8, "breakPointsSaved": 1, "breakPointsFaced": 4 } }
```

Every row has a `type` field (`ranking`, `eloRating`, `match`, `tournament`, `player`, `playerMatch`, `headToHead`, `h2hMatch`) and a `scrapedAt` timestamp. Download as JSON, CSV, Excel or HTML, or read it through the Apify API.

### Coverage and limits

- Rankings and matches cover the ATP and WTA tours, including Grand Slams. Challenger and ITF matches are not in the scores feed.
- Official rankings return the top 150 per tour. The Elo list covers a few hundred active players per tour.
- Detailed serve/return statistics exist for tour-level matches where the source has them. Some rows (qualifying, walkovers, team events) have `stats: null`.
- ATP player match files cover the whole career. WTA head-to-heads and match logs also load the career file when the source has one; player profiles use recent seasons.
- Live scores show the state at the moment of the run. Schedule the Actor (for example every 5 minutes) to keep a feed up to date.

### Pricing

You pay per result row saved to the dataset. Set `maxResults` or a maximum charge per run to control cost.

### FAQ

**Do I need player IDs?** No. Full names, last names and names without accents all work.

**Can I get historical results?** Yes. Use Matches with a past date range (up to 31 days per run), or Player match stats for career match logs.

**Is it legal to scrape tennis data?** The Actor only reads publicly available pages and feeds, without logging in. Check the source sites' terms and your local laws for how you use the data.

# Actor input Schema

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

Rankings: official ATP/WTA top 150 with Elo ratings added, or the full Elo list. Matches: live, finished and scheduled matches with set scores. Tournaments: tournament calendar with champions. Players: profile, ranking, Elo and serve/return stats. Player matches: match-by-match results with full serve and return statistics. Head-to-head: record between two players plus every meeting.

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

ATP (men), WTA (women) or both.

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

Player names, no IDs needed, e.g. <code>Jannik Sinner</code>, <code>Sabalenka</code>. For head-to-head enter pairs: <code>Sinner vs Alcaraz</code>. In Matches mode this filters matches to these players.

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

Official: current ATP/WTA top 150 with points and movement. Elo: the full Elo rating list (overall, hard, clay, grass), a few hundred players per tour.

## `includeElo` (type: `boolean`):

Adds overall, hard, clay and grass Elo to each official ranking row.

## `date` (type: `string`):

Matches: the day to fetch (YYYY-MM-DD, or today / yesterday / tomorrow). Default today. Tournaments: start of the range, default 1 January of this year.

## `endDate` (type: `string`):

Optional end of the date range (YYYY-MM-DD). Matches: up to 31 days. Tournaments: default 31 December of this year.

## `matchStatus` (type: `array`):

Keep only these match states. Leave empty for all.

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

Singles, doubles or both.

## `wholeTournament` (type: `boolean`):

Return every match of the tournaments running on the date, not only the matches played that day.

## `year` (type: `integer`):

Player match stats and head-to-head: keep only matches from this year.

## `surface` (type: `string`):

Player match stats and head-to-head: keep only matches on this surface.

## `lastNMatches` (type: `integer`):

Player match stats: keep only the most recent N matches per player. 0 = all.

## `includeH2HMatches` (type: `boolean`):

Besides the head-to-head summary row, add one row per meeting with full match stats.

## `maxResults` (type: `integer`):

Stop after this many rows. Useful to cap cost.

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

Only used as a fallback if a request is blocked. The default Apify datacenter proxy is enough.

## Actor input object example

```json
{
  "mode": "rankings",
  "tours": [
    "atp",
    "wta"
  ],
  "players": [
    "Jannik Sinner"
  ],
  "rankingType": "official",
  "includeElo": true,
  "date": "today",
  "matchTypes": [
    "singles"
  ],
  "wholeTournament": false,
  "lastNMatches": 0,
  "includeH2HMatches": true,
  "maxResults": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All rows saved by the run. The 'type' field says what each row is: ranking, eloRating, match, tournament, player, playerMatch, headToHead or h2hMatch.

# 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 = {
    "mode": "rankings",
    "tours": [
        "atp",
        "wta"
    ],
    "players": [
        "Jannik Sinner"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("siftwright_data/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 = {
    "mode": "rankings",
    "tours": [
        "atp",
        "wta",
    ],
    "players": ["Jannik Sinner"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("siftwright_data/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 '{
  "mode": "rankings",
  "tours": [
    "atp",
    "wta"
  ],
  "players": [
    "Jannik Sinner"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call siftwright_data/tennis-data-scraper --silent --output-dataset

```

## MCP server setup

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