# Tennis Match Tracker: Results, Form, H2H & Stats (`precious_bathmat/tennis-match-tracker`) Actor

Tennis results, fixtures and live matches across ATP, WTA, Challenger and ITF. Every match comes with both players' recent form, surface record, head-to-head and serve and return statistics, measured before the match started. No API key, no proxy.

- **URL**: https://apify.com/precious\_bathmat/tennis-match-tracker.md
- **Developed by:** [Mariam Ahmed](https://apify.com/precious_bathmat) (community)
- **Categories:** Sports, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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.

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 Tracker: Results, Form, H2H & Stats

Tennis results, today's matches and tomorrow's order of play across **ATP, WTA, Challenger and ITF**, singles and doubles. Every match comes with the part a scoreboard leaves out: **both players' recent form, their record on this surface, rest days, the head-to-head and the match statistics**.

Other tennis scrapers return match rows. A match row tells you who played. It does not tell you that one player has lost three in a row on hard courts while the other has not played in three weeks. This Actor puts that next to every match, ready for a spreadsheet, a model or a pre-match check.

### What does this do?

For each day you ask for, it reads every tennis match listed, keeps the tours and players you want, and for each match adds:

```
Match: Shevchenko A. vs Hurkacz H.  ·  ATP Chengdu, hard  ·  scheduled 24 Sep 11:05 UTC
                          Shevchenko        Hurkacz
  Last 10 matches         3-7  (L1)         7-3  (L1)
  Last 10 on hard         3-7  (L3)         7-3
  Days since last match   5.0               21.7
  Matches in last 14 days 2                 0
  Head-to-head            0-4 (all four on hard, last: United Cup, Jan 2025)
```

For played matches it also adds the **full score with tiebreaks** and **serve and return statistics** for both players.

### Who is it for?

- **Tennis bettors and tipsters** — form, surface record, fatigue and H2H for the whole day's card in one run, instead of opening forty match pages
- **Model builders** — clean, consistent rows with form measured *before* each match started, so results never leak into the features
- **Fantasy and media sites** — results, fixtures and talking points ("0-4 in their meetings") as data
- **Coaches and analysts** — serve and return numbers for any player's recent matches, including Challenger and ITF

### What data do you get?

One row per match:

| Field | What it tells you |
|---|---|
| `startTime`, `tournament`, `category`, `surface`, `qualifying` | When, where and at what level |
| `status` | `scheduled`, `live`, `finished`, `retired`, `walkover`, `cancelled`… |
| `player1`, `player2` (+ country) | Who is playing. Doubles pairs are listed as `A./B.` |
| `score`, `sets`, `winner` | `6-7(5) 6-3 3-6`, and each set with tiebreak points |
| `player1Form`, `player2Form` | Record, win rate, W/L sequence and streak over the last N matches, the same on this surface, days since last match, matches in the last 14 days, last opponent |
| `headToHead` | Meetings, record, record on this surface, last meeting (date, event, winner, sets) |
| `stats` | Aces, double faults, 1st serve %, 1st and 2nd serve points won, break points saved and converted, return points won, service and return games won, total points won |
| `url` | The match page |

Flat columns (`player1FormRecord`, `player1SurfaceRecord`, `h2hRecord`…) make the table view and CSV export readable at a glance.

### How form is measured, and why it matters

**Form is always counted from matches completed before this match started.** That sounds obvious, but the source lists each player's matches including the one being looked at, and anything played since. Counting those would put the result of a finished match into its own "form", which quietly ruins any backtest built on it. This Actor removes them, so a row for last Tuesday's match shows exactly what was knowable on Tuesday morning.

Also:

- **Walkovers are left out** of form (no match was played). **Retirements count**, because a match was.
- **Form covers every level** the source lists: tour, Challenger, ITF and team events. A player winning at Challenger level is winning.
- **Surface form** uses the same window on the same surface. Indoor hard counts as hard.
- **Doubles pairs** get form and H2H too.

### Example: two days of ATP, WTA and Challenger singles

| Measured on 24 September 2026 | Result |
|---|---|
| Days read | 4 (two back, today, one ahead) |
| Matches listed across all tennis | 1,297 |
| Matches kept by the filters | **225** |
| Form and H2H found | **225 of 225** |
| Statistics found for played matches | **171 of 171** |
| Failures | none |

Upcoming matches from that run, straight from the table view:

| Match | Form (last 10) | H2H |
|---|---|---|
| Andreeva M. vs Fernandez L. (Singapore) | 7-3 vs 6-4 | 2-2 |
| Shevchenko A. vs Hurkacz H. (Chengdu) | 3-7 vs 7-3 | 0-4 |
| Sakkari M. vs Hibino N. (Singapore) | 6-4 vs 7-3 (W3) | 2-0 |
| Hijikata R. vs Rublev A. (Hangzhou) | 5-5 vs 6-4 | 0-0 |

### What it refuses to do

- **No picks, tips or win probabilities.** These are records of what has already happened. What you conclude from them is yours.
- **No odds.** For bookmaker prices, see [Betting Odds Comparison](https://apify.com/precious_bathmat/betting-odds-comparison) and [Value Bet Finder](https://apify.com/precious_bathmat/value-bet-finder).
- **No invented numbers.** A player with no earlier completed match gets `null` form, not `0-0`. A match without point data gets `null` statistics, not zeros.

### Limits worth knowing

- **Seven days either side of today**, in UTC. That is the window the source keeps. For a player's longer history, the form fields reach back up to 50 matches.
- **Upcoming matches appear once the order of play is published.** One day ahead is usually full; three or more days ahead is usually nearly empty.
- **Statistics depend on coverage.** ATP, WTA and Challenger matches normally have the full set. Many ITF matches have none, and are returned with `stats: null`.
- **Surface is missing for some team events** (Billie Jean King Cup rows often carry none at the source). Those rows have `surface: null` and no surface form, rather than a guess.
- **Player search works within the chosen days.** Asking for "Sinner" returns his matches in the window, each with his form over the last N matches.

Data comes from Flashscore's public match pages. No API key, account or proxy is needed.

### Pricing

**$0.001 per match**, plus **$0.005 per match insight** when form, head-to-head or statistics are added and found.

A full day of ATP and WTA singles (about 50 matches) with form and statistics costs about **$0.30**. Switch off form and statistics for a plain results feed at **$0.05 per 50 matches**.

### Input

| Field | Meaning |
|---|---|
| **Tours and events** | ATP, WTA, Challenger and ITF, singles and doubles separately, plus team events |
| **Days back / Days ahead** | The window, 0 to 7 days each way (UTC) |
| **Match status** | All, completed only, upcoming only, or live now |
| **Players** | Only matches involving these players; surname is enough |
| **Tournaments** | Only these events, by any part of the name |
| **Include qualifying matches** | Qualifying rounds, flagged on each row |
| **Add form and head-to-head** | Form, surface record, rest and H2H for both players |
| **Form window** | How many earlier matches form uses (3 to 50, default 10) |
| **Add match statistics** | Serve and return statistics for played matches |
| **Maximum matches** | A cap, earliest match first |

### Integrations

Results export to JSON, CSV, Excel and Google Sheets, or go straight into a sheet, a dashboard or a model through the Apify API, webhooks, Make, Zapier and n8n. Schedule it every morning for the day's order of play with form attached, or every night for the day's results.

# Actor input Schema

## `categories` (type: `array`):

Which levels of tennis to include. Singles and doubles are separate choices.

## `daysBack` (type: `integer`):

How many days of results to include before today (UTC). 0 means today only. Flashscore keeps seven.

## `daysAhead` (type: `integer`):

How many days of upcoming matches to include after today (UTC). Matches usually appear only once the order of play is published, so more than one or two days ahead is often thin.

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

Completed matches include retirements and walkovers; each row says which.

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

Only matches involving these players. Surname is enough ("Sinner"), full names and accents are handled. Leave empty for every player.

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

Only these tournaments, by any part of the name, e.g. "Tokyo" or "Shanghai". Leave empty for all.

## `includeQualifying` (type: `boolean`):

Qualifying rounds are listed separately by Flashscore and each row is flagged.

## `includeForm` (type: `boolean`):

Each player's recent record, record on this surface, streak, days since last match and matches in the last 14 days, plus their head-to-head. Measured before the match started. Charged as a match insight.

## `formMatches` (type: `integer`):

How many previous completed matches the form figures use.

## `includeStats` (type: `boolean`):

Aces, double faults, serve and return points won, break points and more, for matches that have been played. Many ITF matches have none.

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

Stops after this many matches, earliest first.

## Actor input object example

```json
{
  "categories": [
    "atp-singles",
    "wta-singles"
  ],
  "daysBack": 1,
  "daysAhead": 1,
  "status": "all",
  "players": [],
  "tournaments": [],
  "includeQualifying": true,
  "includeForm": true,
  "formMatches": 10,
  "includeStats": true,
  "maxMatches": 300
}
```

# Actor output Schema

## `matches` (type: `string`):

Every match found, earliest first, with each player's form and the head-to-head.

## `summary` (type: `string`):

Counts by status and tour, feed coverage, and the stated limits of the data.

# 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 = {
    "players": [],
    "tournaments": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("precious_bathmat/tennis-match-tracker").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 = {
    "players": [],
    "tournaments": [],
}

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

```

## MCP server setup

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

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/JGCscDr8GicIkVCTR/builds/M7yuCEvU9qfzOqSPh/openapi.json
