# Tennis Match History - Career Results & Stats (`crawlplant/tennis-match-history`) Actor

Every match of a tennis player's career, newest first: ATP, WTA, Challenger and ITF results back to 1990 with score, round, surface, both players' Elo before the match and serve / return statistics. Type a name, get the whole career. Works as an MCP tool for AI agents.

- **URL**: https://apify.com/crawlplant/tennis-match-history.md
- **Developed by:** [CrawlPlant](https://apify.com/crawlplant) (community)
- **Categories:** Sports, AI
- **Stats:** 2 total users, 2 monthly users, 99.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 matches

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

## Tennis Match History - Career Results & Stats

*Independent tool, not affiliated with, endorsed by or connected to Flashscore, Livesport, the ATP, the WTA or the ITF. It
reads the public match data that flashscore.com shows to every visitor.*

**Type a player's name, get every match of their career.** ATP, WTA, Challenger and ITF singles and doubles back to 1990,
newest first, **one row per match**: date, tournament, round, surface, opponent, set-by-set score, winner, duration,
**both players' Elo rating before the match** with the Elo win probability, and **serve and return statistics** (aces,
double faults, 1st serve %, points won on 1st and 2nd serve, break points saved and converted).

### Why this one

- **The whole career, not the last 40 results.** Jannik Sinner: 544 singles matches from his first ITF event in 2018
  (476 ATP, 42 Challenger, 26 ITF). Novak Djokovic: 1,444. One run, a few seconds.
- **Names work.** "Jannik Sinner", "Swiatek" or "Alcaraz" finds the player; paste a Flashscore player URL to pick one
  exactly.
- **Statistics included.** Serve and return statistics for ATP and WTA matches from 2012 (458 of Sinner's 544 matches),
  at no extra cost.
- **Elo before every match.** Overall and per surface, computed from every match since 1990, with the win probability it
  gave: test a model on years of history without leaking the result into the rating.
- **Head-to-head in one filter.** Add the opponent's name and get every meeting: Sinner v Alcaraz returns their 18 tour
  meetings (exhibitions are left out unless you ask for them).
- **Updated every hour** from a database of more than 900,000 matches (ATP and WTA from 1990, Challenger from 2008, ITF
  from 2011).
- **See it live.** A career head-to-head with every meeting and the Elo leaders of both tours, from the same database
  your runs read: [crawlplant.com/tennis-match-history](https://crawlplant.com/tennis-match-history/).

### What can you use it for?

- **Betting and prediction models**: years of results per player with pre-match Elo and serve statistics.
- **Scouting and journalism**: a player's record on a surface, at the Grand Slams or against one rival.
- **Fantasy and apps**: career data for player pages without maintaining your own scraper.
- **AI agents**: ask for any player's history as JSON.

### Quick start

1. Click **Try for free** with the default input (Jannik Sinner's 200 latest matches).
2. Replace **Players** with the names you want, or raise **Max results** for whole careers.
3. Download the table as CSV, Excel or JSON, or save the input as a **task** and schedule it to keep a dataset current.

#### Copy to your AI assistant

Paste this into ChatGPT, Claude or any agent so it knows how to use the Actor:

```
crawlplant/tennis-match-history on Apify: every match of a tennis player's career (ATP, WTA, Challenger, ITF, back to
1990), newest first. Input: playerUrls (names like "Jannik Sinner" or Flashscore player URLs), players (opponent name
contains: head-to-head), surfaces (hard/clay/grass/carpet), tournaments (name contains, e.g. "Wimbledon"), tours,
matchType (singles/doubles/all), status (finished/scheduled/live/all), includeStatistics, maxItems (default 200).
Row: date, tournament, round, surface, player1Name, player2Name, score, sets, winnerName, durationMinutes, player1Elo,
player2Elo (pre-match), player1EloWinProbability, statistics (aces, serve %, break points...), url.
Price: $1.00 per 1,000 matches on the Free plan.
```

### Ready-to-use examples

**1. A player's whole career**

```json
{ "playerUrls": ["Jannik Sinner"], "maxItems": 2000 }
```

**2. Every meeting of two rivals**

```json
{ "playerUrls": ["Jannik Sinner"], "players": ["Alcaraz"] }
```

**3. Clay-court matches of several players**

```json
{ "playerUrls": ["Carlos Alcaraz", "Iga Swiatek"], "surfaces": ["clay"], "maxItems": 1000 }
```

**4. Grand Slam matches only**

```json
{ "playerUrls": ["Novak Djokovic"], "tournaments": ["Australian Open", "French Open", "Wimbledon", "US Open"], "maxItems": 1000 }
```

**5. A player's next scheduled matches**

```json
{ "playerUrls": ["Coco Gauff"], "status": "scheduled" }
```

**6. One exact player by URL, Challenger and ITF years only**

```json
{ "playerUrls": ["https://www.flashscore.com/player/sinner-jannik/6HdC3z4H/"], "tours": ["challenger-men", "itf-men"] }
```

**7. Doubles career**

```json
{ "playerUrls": ["Rajeev Ram"], "matchType": "doubles", "maxItems": 1000 }
```

### How to…

#### Download a tennis player's match history

Put the name in `playerUrls` and set `maxItems` above the career length (example 1): a long career has 1,000-1,500
singles matches. Rows come newest first.

#### Get the head-to-head of two players, match by match

One player in `playerUrls`, the other in `players` (example 2). Each row is one meeting with its score, round and both
players' Elo at the time.

#### Compare players on one surface or at the Grand Slams

`surfaces` keeps hard, clay, grass or carpet (example 3); `tournaments` keeps tournaments whose name contains any of the
words (example 4).

### Input options

| Option | Default | Description |
|---|---|---|
| `playerUrls` | `Jannik Sinner` | Player names or Flashscore player page URLs |
| `players` | `[]` | Opponent name contains: keep only matches against these players |
| `surfaces` | all | `hard`, `clay`, `grass`, `carpet` |
| `tournaments` | `[]` | Tournament name contains, e.g. `Wimbledon`, `Masters` |
| `tours` | all but exhibitions | `atp`, `wta`, `challenger-men`, `challenger-women`, `itf-men`, `itf-women`, `teams`, `other` |
| `matchType` | `singles` | `singles`, `doubles`, `all` |
| `status` | `finished` | `finished`, `scheduled`, `live`, `all` |
| `includeStatistics` | `true` | Serve and return statistics (included in the price) |
| `maxItems` | `200` | Maximum matches in total |

### Example output

Sinner's 2026 Wimbledon final (shortened: 2 of the match statistics, no player objects):

```json
{
  "recordType": "match",
  "matchId": "n5DWLXD5",
  "url": "https://www.flashscore.com/match/n5DWLXD5/",
  "date": "2026-07-12",
  "startTime": "2026-07-12T15:10:00.000Z",
  "tour": "atp",
  "tournament": "Wimbledon",
  "round": "Final",
  "surface": "grass",
  "status": "finished",
  "player1Name": "Sinner J.",
  "player2Name": "Zverev A.",
  "player1Elo": 2717,
  "player2Elo": 2483,
  "player1EloWinProbability": 0.7613,
  "winner": "player1",
  "winnerName": "Sinner J.",
  "score": "6-7(7) 7-6(2) 6-3 6-4",
  "durationMinutes": 228,
  "elo": { "player1": 2717, "player2": 2483, "player1Surface": 2157, "player2Surface": 1988, "basis": "pre-match" },
  "statistics": {
    "match": {
      "aces": { "player1": 15, "player2": 17 },
      "winners": { "player1": 58, "player2": 49 }
    }
  },
  "source": "cache"
}
```

### Output fields

| Field | Description |
|---|---|
| `matchId`, `url` | Flashscore's match id and page |
| `date`, `startTime`, `endTime` | Day (UTC) and times (ISO) |
| `tour`, `category`, `tournament`, `tournamentStage`, `round` | `atp`, `wta`, `challenger-men`...; "Qualification" or null for the main draw; "Final", "Semi-finals", "Q1" |
| `surface`, `indoor`, `tournamentCountry` | hard / clay / grass / carpet, indoor court, host country |
| `player1`, `player2`, `player1Name`, `player2Name` | Both sides with Flashscore ids, URLs and countries (both players of a doubles pair) |
| `score`, `sets`, `setsPlayer1`, `setsPlayer2` | "6-7(7) 7-6(2) 6-3 6-4" and each set with tiebreak points |
| `winner`, `winnerName`, `status` | `player1` / `player2`; finished, retired, walkover, scheduled... |
| `durationMinutes` | Match length |
| `player1Elo`, `player2Elo`, `player1EloWinProbability`, `elo` | Elo before the match, overall and on the surface |
| `statistics` | Serve and return statistics for the match and each set (ATP and WTA from 2012) |
| `scrapedAt`, `source` | When the run read it; `cache` = our tennis database, `live` = read from Flashscore in the run |

### How is Elo calculated?

Every singles match since 1990 updates both players' ratings (a K-factor that shrinks with the number of matches played,
plus a separate rating per surface). A row keeps the ratings both players had **before** the match, so a result never
counts in its own numbers.

### How much does tennis match history data cost?

Pay per result: platform usage is included. One price per match, statistics included.

| Event | No discount (Free plan) | Bronze (Starter) | Silver (Scale) | Gold (Business) |
|---|---|---|---|---|
| Match (per 1,000) | $1.00 | $0.90 | $0.80 | $0.70 |
| Actor start (per run) | $0.00005 | $0.00005 | $0.00005 | $0.00005 |

| Example on the Free plan | Matches | Cost |
|---|---|---|
| Default run: Sinner's 200 latest matches | 200 | ~$0.20 |
| Sinner's whole career | 544 | ~$0.54 |
| Djokovic's whole career | 1,444 | ~$1.44 |
| Sinner v Alcaraz, every meeting | 18 | ~$0.02 |

Set a **maximum cost per run** in the run options to stop a large run at your budget.

### Reliability

- Careers come from our tennis database, updated every hour; a run doesn't depend on scraping hundreds of pages.
- If the database can't be reached, the run falls back to the player's Flashscore page (about 40 recent results) and
  says so in the log.
- A name that matches several players takes the best-known one and names the others in the run's warnings.

### Run it through the API

JavaScript:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('crawlplant/tennis-match-history').call({ playerUrls: ['Jannik Sinner'], players: ['Alcaraz'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((m) => `${m.date} ${m.tournament} ${m.round}: ${m.winnerName} ${m.score}`));
```

Python:

```python
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("crawlplant/tennis-match-history").call(run_input={"playerUrls": ["Iga Swiatek"], "maxItems": 2000})
for m in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(m["date"], m["tournament"], m["winnerName"], m["score"])
```

### Use with AI agents

The Actor works as a tool in the [Apify MCP server](https://mcp.apify.com) for Claude, ChatGPT, Cursor and others:

```
https://mcp.apify.com?tools=crawlplant/tennis-match-history
```

Try *"how has Sinner done against Alcaraz on clay?"*.

**Use it when** you need a player's matches from a name: a whole career, one surface or tournament, or every meeting with one rival.

**Not for** bookmaker odds, form or rankings next to each match (use the [Flashscore Tennis Scraper](https://apify.com/crawlplant/flashscore-tennis)).

### More tennis data

- [Flashscore Tennis Scraper](https://apify.com/crawlplant/flashscore-tennis): every match of any day with odds from about
  20 bookmakers, head-to-head, form, rankings, player statistics and Elo lists.
- [Tennis Point-by-Point Data](https://apify.com/crawlplant/tennis-point-by-point): every game and point of matches back to 2012.
- [ATP & WTA Tennis Match Dataset](https://apify.com/crawlplant/tennis-match-dataset): whole seasons in Jeff Sackmann's CSV columns.

### FAQ

#### How far back does it go?

ATP and WTA matches from 1990, Challenger from 2008, ITF from 2011. Serve and return statistics: ATP and WTA from 2012.

#### Does it include doubles?

Yes, `matchType: "doubles"` (example 7); both players of each pair are in the row.

#### Is it legal?

The data is public sporting results read without logging in, at a polite pace. How you use and publish them is your
responsibility, so check the source's terms for your use case.

Questions, a missing field or a source you need? Open an issue on this Actor: we reply within a day. If it saved you time, a short review helps others find it.

### Privacy

The data is about professional players' public sporting results. Each run sends the developer anonymous feature-usage
statistics (the options used, never the players you asked for); your Apify account id is replaced by a one-way hash.

# Actor input Schema

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

Player names, e.g. "Jannik Sinner" or "Swiatek" (the best-known player of that name), or Flashscore player page URLs, e.g. https://www.flashscore.com/player/sinner-jannik/6HdC3z4H/, to pick one exactly. You get every match of each, newest first.

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

Keep only matches against these opponents (name contains, case-insensitive), e.g. Alcaraz. Empty = every opponent.

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

Keep matches on these surfaces. Empty = all. Matches without a surface (team ties) are dropped when this is set.

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

Keep matches whose tournament name or category contains any of these, case-insensitive, e.g. Beijing, US Open, W100, Challenger.

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

Keep only these tours. The default leaves out other = exhibitions (e.g. Six Kings Slam), juniors and the rest; empty = all.

## `matchType` (type: `string`):

singles (default), doubles (includes mixed doubles), or all (also team ties).

## `status` (type: `string`):

finished (default: played matches, including retired, walkover and awarded), scheduled (the player's next matches), live, or all.

## `includeStatistics` (type: `boolean`):

Serve and return statistics for matches that started: aces, double faults, 1st serve %, points won on 1st / 2nd serve, break points saved and converted, service and return games won, for the match and each set. Included in the base price.

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

Maximum matches to return, counted after the filters. A long career has 1,000-1,500 singles matches.

## Actor input object example

```json
{
  "playerUrls": [
    "Jannik Sinner"
  ],
  "players": [],
  "surfaces": [],
  "tournaments": [],
  "tours": [
    "atp",
    "wta",
    "challenger-men",
    "challenger-women",
    "itf-men",
    "itf-women",
    "teams"
  ],
  "matchType": "singles",
  "status": "finished",
  "includeStatistics": true,
  "maxItems": 200
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "playerUrls": [
        "Jannik Sinner"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlplant/tennis-match-history").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 = { "playerUrls": ["Jannik Sinner"] }

# Run the Actor and wait for it to finish
run = client.actor("crawlplant/tennis-match-history").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 '{
  "playerUrls": [
    "Jannik Sinner"
  ]
}' |
apify call crawlplant/tennis-match-history --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlplant/tennis-match-history"
        }
    }
}
```

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/S6rUDg2deh1i8Xegp/builds/TQ4sXXlvz90SJ45aV/openapi.json
