# TennisExplorer Match Results Scraper (`automation-lab/tennisexplorer-match-results-scraper`) Actor

Export TennisExplorer ATP and WTA match results by date with players, events, set scores, status, available odds and source URLs.

- **URL**: https://apify.com/automation-lab/tennisexplorer-match-results-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Sports
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.48 / 1,000 item extracteds

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?

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 Match Results Scraper

Build clean, recurring tennis results datasets from **TennisExplorer** without maintaining selectors, date loops, score parsing, or Apify storage code. The Actor exports one typed row per ATP or WTA match, including players, event context, set scores, completion status, available decimal odds and stable source URLs.

Use it for one-day analysis, historical date ranges up to 31 days, exact TennisExplorer result pages, or scheduled sports-data pipelines. Results are written to the default Apify dataset for immediate JSON, CSV, Excel, XML, API and webhook use.

### What can this TennisExplorer scraper extract?

- ATP and WTA singles match rows
- result date and displayed match time
- tournament or event name and event URL
- event country code when exposed by the results page
- both player names and profile URLs
- seed or rank annotation when shown beside a player
- completed set totals and set-by-set scores
- derived winner and match status
- available home/away decimal odds
- match-detail source URL and exact results-page URL
- scrape timestamp for pipeline auditing

`round` and `surface` are included as nullable compatibility fields. They remain `null` when the selected results page does not expose those values; the Actor does not invent them.

### Who is it for?

**Sports analysts** can create repeatable result tables for form, event and player analysis.

**Data engineers** can schedule a daily run and append normalized rows to a warehouse or data lake.

**Tennis researchers** can export a bounded historical window without manually copying results pages.

**Model builders** can join source-attributed scores and available odds to their own licensed feature sets.

**Publishers and dashboards** can use webhooks or the dataset API to refresh completed-match feeds.

### Why use this Actor?

1. It reads the server-rendered TennisExplorer results table directly, so no expensive browser is required.
2. It pairs the two source rows that make up a match and returns one integration-friendly object.
3. It validates dates, URLs, ranges and limits before scraping.
4. It retries only temporary network, rate-limit and server failures.
5. It deduplicates by TennisExplorer match ID across supplied pages.
6. It stops at `maxItems`, including in multi-day workflows.
7. It fails on blocked or unexpected response shapes instead of silently returning a misleading empty dataset.

### Getting started

1. Open the Actor input page in Apify Console.
2. Keep the prefilled date or choose a new `date` in `YYYY-MM-DD` format.
3. Select `all`, `atp`, or `wta` under **Tour**.
4. Set **Maximum matches** to the number of rows you need.
5. Click **Start**.
6. Open the run's **Dataset** tab to preview or download results.
7. Save the input as an Apify Task if you want a schedule, webhook, or reusable configuration.

A useful small input is:

```json
{
  "date": "2026-08-22",
  "tour": "all",
  "maxItems": 20
}
```

### Input parameters

| Field | Type | Default | Purpose |
|---|---:|---:|---|
| `date` | string | prefilled date | One result day in `YYYY-MM-DD` format. |
| `fromDate` | string | — | First day of an inclusive date range. |
| `toDate` | string | — | Last day of an inclusive date range. |
| `tour` | string | `all` | `all`, `atp`, or `wta` singles. |
| `startUrls` | array | — | Exact `tennisexplorer.com/results/` URLs containing year, month and day. |
| `maxItems` | integer | `100` | Global result limit from 1 to 10,000. |

Supplied `startUrls` take precedence over date fields. When URLs are not supplied, `fromDate` and `toDate` take precedence over `date`. A range may cover at most 31 inclusive days.

### Use an exact TennisExplorer results URL

Use source-specific URLs when another system already produces a TennisExplorer day link:

```json
{
  "startUrls": [
    {
      "url": "https://www.tennisexplorer.com/results/?type=atp-single&year=2026&month=08&day=22"
    }
  ],
  "maxItems": 10
}
```

Only public TennisExplorer `/results/` URLs are accepted. Player, ranking, match-detail and unrelated URLs fail validation rather than producing partial output.

### Build a multi-day WTA dataset

Use an inclusive range for a bounded backfill or recurring export:

```json
{
  "fromDate": "2026-08-20",
  "toDate": "2026-08-22",
  "tour": "wta",
  "maxItems": 50
}
```

For longer history, split work into multiple scheduled or API runs. This keeps each run observable and reduces the impact of upstream changes.

### Output fields

| Field | Meaning |
|---|---|
| `matchId` | Stable TennisExplorer match-detail ID when available. |
| `date`, `time` | Requested result date and source-displayed time. |
| `status` | `completed`, `scheduled`, or `unknown`, derived from source score cells. |
| `tour` | `ATP`, `WTA`, or `null` when the event header does not identify one. |
| `event` | Tournament/event label. |
| `eventCountryCode` | Two-letter country code from source markup, when present. |
| `eventUrl` | Absolute TennisExplorer event URL. |
| `round`, `surface` | Nullable fields; no value is inferred when not exposed. |
| `player1`, `player2` | Name, profile URL and nullable seed/rank annotation. |
| `winner` | `1`, `2`, or `null`. |
| `score` | Compact completed-match score. |
| `player1SetsWon`, `player2SetsWon` | Completed set totals when exposed. |
| `setScores` | Ordered per-set score objects. |
| `player1Odds`, `player2Odds` | Source decimal odds, or `null`. |
| `sourceUrl` | Match-detail URL, falling back to the results page. |
| `resultsPageUrl` | Exact fetched date page. |
| `scrapedAt` | ISO 8601 collection time. |

### Example output

The exact matches change with the selected date. A current row follows this shape:

```json
{
  "matchId": "3300757",
  "date": "2026-08-22",
  "time": "01:15",
  "status": "completed",
  "tour": "ATP",
  "event": "Cincinnati",
  "eventCountryCode": "US",
  "eventUrl": "https://www.tennisexplorer.com/cincinnati/2026/atp-men/",
  "round": null,
  "surface": null,
  "player1": {
    "name": "Tiafoe F.",
    "profileUrl": "https://www.tennisexplorer.com/player/tiafoe/",
    "seedOrRank": 17
  },
  "player2": {
    "name": "Nakashima B.",
    "profileUrl": "https://www.tennisexplorer.com/player/nakashima-68876/",
    "seedOrRank": 27
  },
  "winner": 1,
  "score": "2-0 (7-5, 6-3)",
  "player1SetsWon": 2,
  "player2SetsWon": 0,
  "setScores": [
    { "set": 1, "player1": "7", "player2": "5" },
    { "set": 2, "player1": "6", "player2": "3" }
  ],
  "player1Odds": 1.96,
  "player2Odds": 1.85,
  "sourceUrl": "https://www.tennisexplorer.com/match-detail/?id=3300757",
  "resultsPageUrl": "https://www.tennisexplorer.com/results/?type=atp-single&year=2026&month=08&day=22",
  "scrapedAt": "2026-08-23T12:00:00.000Z"
}
```

### How much does it cost to extract TennisExplorer match results?

Pay-per-event pricing has a **$0.005 run start** and one `item` event for each match saved. The BRONZE item price is **$0.005808 per match**, with lower per-item prices on higher platform tiers.

| Saved matches | BRONZE example |
|---:|---:|
| 1 | about $0.010808 |
| 20 | about $0.12116 |
| 100 | about $0.5858 |
| 1,000 | about $5.813 |

The Actor does not charge the item event for malformed, duplicate or unsaved rows. Apify platform tier and rounding can affect the final displayed charge; check the run's charge events for the authoritative amount.

### Scheduling and recurring workflows

Create an Apify Task with `date` omitted to use the current UTC day at run time. Then attach a daily schedule. Each run produces a separate default dataset and can trigger a webhook when it succeeds.

Common pipelines include:

- daily results to BigQuery or Snowflake;
- a webhook to update a sports dashboard;
- a bounded backfill followed by daily incremental runs;
- CSV exports for analysts;
- source-URL audits for downstream corrections;
- player/event joins using stable profile and event URLs.

Use your destination's stable key strategy, such as `matchId`, when merging repeated dates.

### Dataset API and exports

Every accepted match is saved to the default dataset. In Apify Console you can download JSON, JSONL, CSV, Excel, XML or RSS. Programmatic clients can read the dataset ID from the run object and request items with pagination or field selection.

The `overview` view presents date, tour, event, players, score, status, odds and source URL. Raw dataset exports retain every schema field.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~tennisexplorer-match-results-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"date":"2026-08-22","tour":"atp","maxItems":20}'
```

Poll the returned run or use `run-sync-get-dataset-items` when the caller can wait for completion.

### JavaScript API example

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/tennisexplorer-match-results-scraper').call({
  fromDate: '2026-08-20',
  toDate: '2026-08-22',
  tour: 'wta',
  maxItems: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Python API example

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/tennisexplorer-match-results-scraper').call(run_input={
    'date': '2026-08-22',
    'tour': 'all',
    'maxItems': 20,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/tennisexplorer-match-results-scraper"
```

#### Claude Desktop, Cursor, and VS Code setup

Use the same HTTP MCP configuration in Claude Desktop, Cursor, or VS Code:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/tennisexplorer-match-results-scraper"
    }
  }
}
```

Example prompts:

- “Run TennisExplorer Match Results Scraper for ATP results on 2026-08-22 and return 20 rows.”
- “Collect WTA results from 2026-08-20 through 2026-08-22 and summarize completed matches by event.”
- “Export the exact TennisExplorer ATP results URL to a dataset and show match IDs and source links.”

### Reliability and limits

TennisExplorer is an independent upstream website. Markup, availability, old-date coverage and displayed odds can change. This Actor uses direct server-rendered HTML and does not require login, proxy or a browser under the verified route.

A successful page with naturally no matches yields zero rows. A blocked response, non-HTML response, invalid URL or unrecognized source shape raises an error. Temporary 429, 5xx and network failures receive up to three bounded attempts.

The Actor does not enrich each match from player or detail pages. Rankings, odds, round and surface are returned only when available in the fetched results-page row.

### Troubleshooting

**The run returns no records**

Confirm the requested date and tour had matches on TennisExplorer. Try `tour: "all"` and inspect the source date page.

**The input fails immediately**

Use valid calendar dates in `YYYY-MM-DD`, keep ranges at 31 days or fewer, and supply only TennisExplorer `/results/` URLs with year, month and day.

**The run reports an unexpected response shape**

The source may be temporarily blocking requests or may have changed its markup. Retry later and include the run URL when reporting a reproducible issue.

**Why are odds, round or surface null?**

Those values are conditional. The Actor preserves `null` rather than inventing unavailable data.

### Responsible use and legality

This Actor accesses publicly visible TennisExplorer pages. You are responsible for ensuring that your collection and downstream use comply with TennisExplorer's terms, applicable database and copyright rules, privacy law, contracts, and local regulations. Keep schedules and limits proportionate. Do not use the output for unlawful gambling activity or present historical odds as current betting advice.

The Actor is an independent automation tool and is not affiliated with or endorsed by TennisExplorer.

### Related Automation Lab Actors

- [Tennis Abstract Player Match History Scraper](https://apify.com/automation-lab/tennis-abstract-player-match-history-scraper) for player-centered historical match rows from a different public source.
- [SofaScore Live Events & Statistics Scraper](https://apify.com/automation-lab/sofascore-live-events-statistics-scraper) for broader scheduled/live event and available-statistics workflows.

Use this Actor when TennisExplorer date-based result pages and source attribution are the required contract.

### FAQ

**Does this provide a TennisExplorer API?**

It provides an Apify API and normalized dataset around supported public TennisExplorer result pages. It is not an official TennisExplorer API.

**Can it scrape ATP and WTA separately?**

Yes. Set `tour` to `atp` or `wta`; `all` returns both when the page exposes them.

**Can it monitor new or changed matches?**

Schedule a Task and merge datasets by `matchId` in your destination. The Actor itself does not maintain cross-run change state or send alerts.

**Does it scrape doubles?**

Discovery mode currently targets all/ATP/WTA result views described in the input. Exact supported results URLs are parsed according to their source table, but doubles-specific completeness is not promised.

**Are odds always included?**

No. `player1Odds` and `player2Odds` are nullable because TennisExplorer does not expose odds for every match.

**Will it follow player profile pages?**

No. It returns linked profile URLs and visible rank/seed annotations without extra profile requests.

**How should I avoid duplicate rows?**

The Actor deduplicates within a run. For recurring runs, upsert on `matchId` in your destination.

# Actor input Schema

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

One result date in YYYY-MM-DD format. Ignored when start URLs or a date range are supplied.

## `fromDate` (type: `string`):

First date of a recurring or historical results range (YYYY-MM-DD). Ranges are limited to 31 days.

## `toDate` (type: `string`):

Last date of the range, inclusive (YYYY-MM-DD).

## `tour` (type: `string`):

Return all singles results or limit discovery to ATP or WTA singles.

## `startUrls` (type: `array`):

Optional exact tennisexplorer.com/results/ URLs containing year, month, and day. URL parameters control the source page.

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

Stop after this many unique match records across all requested dates.

## Actor input object example

```json
{
  "date": "2026-08-22",
  "tour": "all",
  "maxItems": 20
}
```

# Actor output Schema

## `overview` (type: `string`):

Open all saved matches in the overview dataset view.

# 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 = {
    "date": "2026-08-22",
    "tour": "all",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/tennisexplorer-match-results-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 = {
    "date": "2026-08-22",
    "tour": "all",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/tennisexplorer-match-results-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 '{
  "date": "2026-08-22",
  "tour": "all",
  "maxItems": 20
}' |
apify call automation-lab/tennisexplorer-match-results-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/tennisexplorer-match-results-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/dIF9vvmQZ2dkH9OGX/builds/jVFd8yndajuBkzpKf/openapi.json
