# TennisExplorer Results (`subrosa/tennisexplorer-results`) Actor

Returns completed professional tennis match results by date from tennisexplorer.com, with tournament, players, score, sets, and closing odds.

- **URL**: https://apify.com/subrosa/tennisexplorer-results.md
- **Developed by:** [Sub Rosa](https://apify.com/subrosa) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 results

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

## TennisExplorer Results

Returns completed professional tennis match results from tennisexplorer.com, one item per match, for the days you name. Each item carries the tournament, the two competitors as the site lists them, the winner, the score in the site's notation, the games per set as numbers, and the decimal odds the site shows next to the result. The source covers ATP, WTA, Challenger, ITF, UTR and team events, singles and doubles, and the default run reads yesterday and the day before, which are complete days. Pages are fetched over plain HTTP, logged out, under the declared identity `SubRosaBot/1.0`, at 30 requests per minute at most. Pay per event: a fixed fee per run and a fee per match delivered.

### Who uses it

- A betting model builder who pulls each morning's finished matches with the closing odds (`odds1`, `odds2`) and the set-by-set games (`sets`) to grade yesterday's predictions and refit the model.
- A tennis statistics site that stores results per tournament (`tournament`, `tournamentUrl`, `surface`, `round`) and needs a stable key per match (`id`) to avoid double counting when the same day is fetched twice.
- A sports newsletter that filters a single event (`tournament: "Chengdu"`) and lists the day's winners and scores (`player1`, `player2`, `score`) without reading a results page by hand.

### Sample output

One item from a real run on 2026-09-25 (ten are in `sample.json`, including a doubles match, a Futures row with no tournament page, a row with no odds, and a walkover):

```json
{
    "id": "3332228",
    "date": "2026-09-25",
    "tournament": "Chengdu",
    "tournamentUrl": "https://www.tennisexplorer.com/chengdu/2026/atp-men/",
    "tour": "ATP",
    "isDoubles": false,
    "round": null,
    "surface": "Hard",
    "player1": "Basilashvili N.",
    "player2": "Van De Zandschulp B.",
    "winner": 1,
    "score": "6-4, 7-5",
    "sets": [
        { "p1": 6, "p2": 4 },
        { "p1": 7, "p2": 5 }
    ],
    "odds1": 2.59,
    "odds2": 1.48,
    "matchUrl": "https://www.tennisexplorer.com/match-detail/?id=3332228",
    "url": "https://www.tennisexplorer.com/match-detail/?id=3332228",
    "sourceUrl": "https://www.tennisexplorer.com/results/?type=all&year=2026&month=09&day=25",
    "scrapedAt": "2026-09-26T03:56:32.689Z"
}
```

### Data dictionary

| Field           | Type            | Meaning                                                                                                                                                                       | Example                                                                        |
| --------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `id`            | string          | The site's own match id, taken from the match-detail link. When a row has no link, a fallback built from the date, tournament and both competitors.                           | `"3332228"`                                                                    |
| `date`          | string          | Day the match is listed under, YYYY-MM-DD, as requested in the input.                                                                                                         | `"2026-09-25"`                                                                 |
| `tournament`    | string          | Tournament name as shown in the results table header.                                                                                                                         | `"Chengdu"`                                                                    |
| `tournamentUrl` | string or null  | Absolute URL of the tournament page. Doubles blocks carry `?type=double`. Null for grouped blocks such as "Futures 2026" that have no page.                                   | `"https://www.tennisexplorer.com/chengdu/2026/atp-men/"`                       |
| `tour`          | string or null  | `"ATP"` for men's events or `"WTA"` for women's events, as the site classifies them. Null when the block carries no marker.                                                   | `"ATP"`                                                                        |
| `isDoubles`     | boolean         | True when the block is a doubles draw; then `player1` and `player2` are teams written as "A / B".                                                                             | `false`                                                                        |
| `round`         | string or null  | Round code as shown on the tournament page: `1R`, `2R`, `R16`, `QF`, `SF`, `F`. Filled only when `includeRounds` is true and the match is on the tournament's main-draw page. | `"R16"`                                                                        |
| `surface`       | string or null  | Court surface as the site labels it: Hard, Clay, Grass, Indoors, Carpet. Null when the site does not show it.                                                                 | `"Hard"`                                                                       |
| `player1`       | string          | Competition name of the first-listed competitor, which on this site is the winner, as displayed. A doubles team is "Arribage T. / Olivetti A.".                               | `"Basilashvili N."`                                                            |
| `player2`       | string          | Competition name of the second-listed competitor, the loser, as displayed.                                                                                                    | `"Van De Zandschulp B."`                                                       |
| `winner`        | integer or null | 1 when `player1` won more sets, 2 when `player2` did, null when the sets-won counts are missing or equal.                                                                     | `1`                                                                            |
| `score`         | string or null  | Set scores in the site's notation, winner's games first, tiebreak loser's points in parentheses. Null when no games are shown (walkover).                                     | `"6-4, 7-5"`                                                                   |
| `sets`          | array           | One `{ p1, p2 }` object per completed set with games won. Empty when no games are shown.                                                                                      | `[{ "p1": 6, "p2": 4 }]`                                                       |
| `odds1`         | number or null  | Decimal odds shown for `player1` on the results page, or null when the site shows none.                                                                                       | `2.59`                                                                         |
| `odds2`         | number or null  | Decimal odds shown for `player2`, or null.                                                                                                                                    | `1.48`                                                                         |
| `matchUrl`      | string or null  | Absolute URL of the site's match-detail page, or null when the row has no link.                                                                                               | `"https://www.tennisexplorer.com/match-detail/?id=3332228"`                    |
| `url`           | string          | Canonical page for the record: the match-detail page, or the results page when there is none.                                                                                 | same as `matchUrl`                                                             |
| `sourceUrl`     | string          | Results page the record was extracted from.                                                                                                                                   | `"https://www.tennisexplorer.com/results/?type=all&year=2026&month=09&day=25"` |
| `scrapedAt`     | string          | Extraction time, ISO 8601, UTC.                                                                                                                                               | `"2026-09-26T03:56:32.689Z"`                                                   |

### Input

| Field                | Type             | Default                | Meaning                                                                                                                                                                                                                            |
| -------------------- | ---------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dates`              | array of strings | `[]`                   | Days to fetch as YYYY-MM-DD, one results page per day. Empty means yesterday and the day before, in UTC. Malformed or impossible dates are skipped with a warning in the log.                                                      |
| `tournament`         | string           | `""`                   | Keep only matches whose tournament name contains this text, case-insensitive. `"challenger"` keeps every Challenger event; `"Chengdu"` keeps one. Empty keeps all.                                                                 |
| `matchType`          | string           | `"all"`                | Which of the site's results tabs to read: `all`, `atp-single`, `atp-double`, `wta-single`, `wta-double`.                                                                                                                           |
| `includeRounds`      | boolean          | `false`                | Also fetch one page per tournament seen, to fill `round` and any `surface` the sidebar did not show. Adds one request per tournament block per run: two full days measured at 54 requests and 70 seconds on 2026-09-25.            |
| `maxItems`           | integer          | `200`                  | Stop after this many matches. A full day lists roughly 240 to 550 matches across all tours (measured at 241 to 549 over the eight days to 2026-09-25), so raise this to fetch whole days. The default keeps the default run short. |
| `proxyConfiguration` | object           | Apify datacenter proxy | Proxy settings. Datacenter proxies are enough; residential proxies are not needed.                                                                                                                                                 |

The results table lists the winner first on every row, so `player1` is the winner and `winner` is 1 unless the site changes that convention; the field is derived from the sets-won counts rather than assumed.

### Pricing

Pay per event, two events. `actor-start`: $0.02, once per run, covering the fixed overhead of starting the Actor and fetching the results pages. `result`: $0.002 per item ($2 per 1,000). A run never delivers more items than it charges for: items are pushed and charged in the same step, and the run stops cleanly when your maximum charge is reached. The prices on the Actor's page are the ones in force if they differ from this text.

Measured platform cost, from a default run on 2026-09-25: 2 requests, 0.98 MB transferred, 190 MB peak memory, 5 seconds locally. Allowing 10 seconds in a 512 MB container on the platform, at Apify's listed $0.20 per compute unit and $0.20 per GB of external transfer, that run costs about $0.0005, which is about $0.0024 per 1,000 items. The `result` price is about 800 times the measured platform cost per item. A run with `includeRounds` for two full days measured at 54 requests, 14.5 MB, 460 MB peak memory and 70 seconds for 676 items; in a 1 GB container that is about $0.007 a run, or $0.010 per 1,000 items, so the `result` price is about 200 times the platform cost on that path.

Free allowance: Apify's Free plan carries $5 of usage a month (checked 2026-09-25). A default run of 200 items costs $0.42 ($0.02 plus 200 times $0.002), so the allowance covers 11 default runs a month, or a single run of up to 2,490 items.

### Limits and honesty

- The results page does not show the round. `round` is null unless `includeRounds` is true, and even then qualifying matches and matches on tournament pages the site serves with an error stay null, because qualifying draws live on a separate tab that is not fetched. In the measured two-day run with `includeRounds`, 364 of 676 items received a round.
- `surface` comes from the site's "this week's tournaments" sidebar, which lists the current week only. For older dates and for ITF or Futures blocks it is often null; `includeRounds` fills it from the tournament page where one exists.
- The site marks neither retirements nor walkovers on the results page. A retired match shows the games played so far and a sets-won count such as 1-0; a walkover shows no games, so `score` is null and `sets` is empty.
- Odds are the odds the site displays next to the result, from whichever bookmaker it uses at the time. They are not a market average and are null for some lower-level matches (13 of 200 items in the default run on 2026-09-25).
- Competitor names are the site's short competition form (`"Basilashvili N."`). The site truncates long doubles names in the visible cell; the full form is taken from the cell's tooltip when present.
- Times, seedings, country flags, head-to-head links, player profile links, streams and news are on the page and are not collected.
- Only days that have finished are complete. Fetching today returns whatever has finished so far.
- A results page for a day with no matches yields zero items and a warning; a run with zero items over all dates fails with a clear error rather than finishing green.
- The source changes its markup a few times a year. When it does, the daily test fails, the house status page shows it, a fix ships, and the changelog records it.

### Legal

robots.txt. tennisexplorer.com/robots.txt, checked 2026-09-25, has one block for every user agent and disallows three paths only: `/redirect/`, `/terms-of-use/` and `/contact/`. The paths this Actor fetches are permitted: `/results/` for the daily results pages, and the tournament pages such as `/chengdu/2026/atp-men/` that `includeRounds` reads. The Actor reads robots.txt at the start of every run, under its own identity, and skips any path it disallows, so a change there stops a fetch without a code change. The Actor never fetches `/terms-of-use/`; the quotation below was taken by a person reading that page on 2026-09-25.

Terms. The site's Terms of Use (https://www.tennisexplorer.com/terms-of-use/), section 2.11, say: "You must not burden our server on which the Website is hosted with automated requests, nor assist any third party in such activity." and "Furthermore, you are not permitted to use our content available on the Website by embedding, aggregating, scraping or recreating it without our express consent, unless otherwise provided for by applicable laws and regulations." The house holds no account at tennisexplorer.com and has never logged in. This Actor fetches only logged-out public pages that robots.txt permits, at 30 requests a minute at most over two connections, under the declared identity `SubRosaBot/1.0 (+https://subrosa.dev/bots)`, which is fixed and never rotated. No cookies are accepted or persisted, no headers are generated to imitate a browser, and no challenge is solved. If tennisexplorer.com asks the house to stop, in any form, the Actor is unpublished the same day and the changelog says why.

Public performers. `player1` and `player2` carry the name of a professional athlete as it appears in the public record of the event they are in, a match result. That name is the record, not the person's data. It is carried in a field that names the role (`player1`, `player2`), never in `name`. Nothing else about the person is collected: no profile URL, no photo, no biography, no ranking history, no birthdate. The results page also links each competitor's profile page and shows seedings and entry marks such as (5), (Q) or (WC); this Actor drops them at extraction and the live test asserts that no item carries a profile URL or an image.

Your side. A buyer with an account at tennisexplorer.com is bound by the terms they accepted there. You are responsible for how you use the data downstream. Odds are shown for record purposes and are not a recommendation.

### Changelog

- 2026-09-26: house scrub module adopted. The live test reads the shared phone and email rules (phone detection by shape, never by digit count) and the schema's identifier keys; `sameDomainDelaySecs` written explicitly as 0 (tennisexplorer.com robots.txt states no Crawl-delay, checked 2026-09-26). No output field changed.
- 2026-09-25: review fixes before publish. Fixed identity on the wire (`SubRosaBot/1.0` on every request including robots.txt, session pool and cookie persistence off); delivery through the house `createDeliverer`; Legal section restated with robots.txt, the terms quotation and the public-performer ruling; item counts and free allowance corrected to measured figures; sample regenerated with doubles, Futures and no-odds rows.
- 2026-09-25: first release.

### Support

Open an issue on the Actor's Issues tab. Replies within one day.

# Changelog

This Actor's version history is a separate document: https://apify.com/subrosa/tennisexplorer-results/changelog.md

# Actor input Schema

## `dates` (type: `array`):

Days to fetch, as YYYY-MM-DD strings, one results page per day. Leave empty for yesterday and the day before (UTC), which are complete days. Malformed dates are skipped with a warning.

## `tournament` (type: `string`):

Keep only matches whose tournament name contains this text, case-insensitive (for example "Chengdu" or "challenger"). Empty keeps every tournament.

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

Which of the site's results tabs to read. "all" covers ATP, WTA, Challenger, ITF and UTR events, singles and doubles.

## `includeRounds` (type: `boolean`):

Also fetch one page per tournament seen to fill the round field (1R, R16, QF, SF, F) and any missing surface. Adds one request per tournament block per run (two full days measured at 54 requests and 70 seconds); qualifying matches stay null.

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

Stop after this many matches. A full day lists roughly 240 to 550 matches across all tours (measured at 241 to 549 over the eight days to 2026-09-25), so raise this to fetch whole days. The default keeps the default run short.

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

Apify proxy settings. Datacenter proxies are enough for this source; residential proxies are not needed and cost more.

## Actor input object example

```json
{
  "dates": [],
  "tournament": "",
  "matchType": "all",
  "includeRounds": false,
  "maxItems": 200,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  }
}
```

# Actor output Schema

## `results` (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 = {
    "dates": [],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": []
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("subrosa/tennisexplorer-results").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 = {
    "dates": [],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": [],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("subrosa/tennisexplorer-results").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 '{
  "dates": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  }
}' |
apify call subrosa/tennisexplorer-results --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,subrosa/tennisexplorer-results"
        }
    }
}
```

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/AhWErwUW1vJwVmWSx/builds/LJuzJfppT3PpkGCRB/openapi.json
