# Tennis Scores & Results – ATP & WTA Live Matches, Draws & Odds (`rowfeed/tennis-scores-results-scraper`) Actor

One row per ATP/WTA match: live scores, set-by-set results with tiebreaks, round and draw, plus matched Kalshi exchange odds. Singles and doubles, today or any date range.

- **URL**: https://apify.com/rowfeed/tennis-scores-results-scraper.md
- **Developed by:** [Rowfeed](https://apify.com/rowfeed) (community)
- **Categories:** Sports, Developer tools
- **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

Get live and finished ATP/WTA tennis matches as one clean JSON row per match: set-by-set scores with tiebreaks, round, draw and court, plus matched Kalshi exchange odds. Built for tennis dashboards, score-alert bots, betting research and AI agents that need to ask "what's the score in this match right now" without a login or a headless browser.
Source data is ESPN's public tennis scoreboard, so scores update from the same feed ESPN's own site uses, and odds come from Kalshi, the CFTC-regulated exchange - real trades, not a bookmaker's model.

### What you get

- **One row per match** – tour, tournament, round, venue/court, scheduled time, status (scheduled/live/final), both players (name, country, singles or doubles pair), set-by-set score with tiebreak points, ESPN's own free-text recap, and `best_of` (3 or 5) once a match has finished normally.
- **Odds matched in for you** – each match is joined to Kalshi's moneyline market for the same tour by player surname and date, while the market is trading (upcoming and live matches); `kalshi` is `null` (never guessed) when no confident match exists or Kalshi has already settled the market.
- **Today, live-first, or any date range** – the default run returns today's live and upcoming singles matches first, finished ones after; set `dateFrom`/`dateTo` for any past or future window (up to 31 days) and switch on doubles.

### Sample row

One live WTA quarterfinal, matched to Kalshi, from an actual run.

```json
{
  "tour": "WTA",
  "tournament": "Singapore Tennis Open presented by BNP Paribas",
  "draw": "Women's Singles",
  "round": "Quarterfinal",
  "venue": "Singapore, Singapore",
  "court": "Center Court",
  "scheduled_time": "2026-09-25T06:55Z",
  "status": "live",
  "status_detail": "2nd Set",
  "player_a": { "name": "Wang Xinyu", "country": "China", "seed": null, "winner": false, "athletes": [{ "name": "Wang Xinyu", "country": "China" }] },
  "player_b": { "name": "Tatiana Prozorova", "country": "Russia", "seed": null, "winner": false, "athletes": [{ "name": "Tatiana Prozorova", "country": "Russia" }] },
  "sets": [
    { "a": 6.0, "b": 3.0, "tiebreak_a": null, "tiebreak_b": null },
    { "a": 5.0, "b": 5.0, "tiebreak_a": null, "tiebreak_b": null }
  ],
  "score_text": "Wang Xinyu (CHN) is tied with Tatiana Prozorova (RUS) 6-3 5-5",
  "best_of": null,
  "kalshi": {
    "event_ticker": "KXWTAMATCH-26SEP24WANPRO",
    "player_a_yes_ask": 0.81,
    "player_b_yes_ask": 0.2,
    "player_a_probability": 0.805,
    "player_b_probability": 0.195,
    "url": "https://kalshi.com/markets/kxwtamatch"
  },
  "url": "https://www.espn.com/tennis/scoreboard/tournament/_/eventId/1009-2026/competitionType/2",
  "scraped_at": "2026-09-25T08:45:56+00:00",
  "match_id": "184104"
}
```

A doubles row's `player_a`/`player_b` carry two entries under `athletes` (e.g. `"David Stevenson / Marcus Willis"`), and `seed` is always `null` - ESPN's scoreboard feed never sends a seed, so this Actor never guesses one. `best_of` is read off the sets the winner needed in a normally finished match (2 sets = best of 3, 3 sets = best of 5). It stays `null` for scheduled and live matches, retirements and walkovers, because ESPN's own format field is wrong for many ATP events.

### Filters

| Input | Default | What it does |
|---|---|---|
| `tours` | `["ATP","WTA"]` | Men's (ATP) and/or women's (WTA) tour. Each match is tagged by its own draw, so a Grand Slam (men's and women's draws in one event) returns each match once, under the right tour. |
| `draws` | `["singles"]` | `singles` and/or `doubles`. |
| `status` | `all` | `all`, `live`, `upcoming` or `finished`. Rows are always sorted live first, then upcoming, then finished. |
| `dateFrom` / `dateTo` | today | UTC dates (`YYYY-MM-DD`). A match is kept only when its own scheduled day falls in the range, not just its tournament's week. Capped at 31 days. |
| `includeKalshi` | `true` | Join each match to its live Kalshi moneyline market (same tour) by player surname + date. |
| `maxMatches` | `200` | Cap on rows, live/upcoming/finished order then soonest-scheduled first (1-1000). |

A tour or date range with nothing scheduled simply contributes zero rows - not an error.

### Pricing

Pay per event, no subscription: **$1 per 1,000 matches** and **$1 per 1,000 run starts** (kept tiny so you can poll a single day at a time). The Kalshi join, when it finds one, rides along on the match row for free. Set a maximum charge on the run and the Actor stops cleanly when it is reached, charging only for rows actually saved.

### Details

- **Sources**: ESPN's public site API (`site.api.espn.com`, no auth) for scores, Kalshi's public trade API v2 (`api.elections.kalshi.com`, no auth) for odds. No proxies, no browser, no login. **Not affiliated with ESPN or Kalshi.**
- **Set scores**: built from ESPN's own structured `linescores`, not parsed out of the text recap - `score_text` is included for reference but `sets` is the reliable field to compute with.
- **The Kalshi join is conservative**: it requires both players' surnames to match one live Kalshi market in the same tour's series (`KXATPMATCH` or `KXWTAMATCH`) on the same date (+/- a day, since Kalshi's contract-expiry timestamp can land a day off the match date). No confident match means `kalshi: null`, never a guess.
- **Reliability**: 429 and 5xx responses are retried with exponential backoff (5 tries), a 200 without the expected data counts as a failure, and one bad request never stops the run - it becomes an error row (`error`, `errorMessage`) and the rest continues. A run fails only when it produced no rows *and* a source failed; a tour or date with nothing scheduled is a successful run with zero charged rows.
- **Run stats**: the `STATS` record in the run's key-value store holds match rows, Kalshi-matched count, error rows, tournaments scanned, request and error counts per category.
- **Output**: one dataset row per match, live first. Export as JSON, CSV or Excel, fetch through the Apify API, or schedule runs and pipe them into Google Sheets, Make, Zapier, n8n or your own code. Eligible for agentic use via Apify's MCP server.

# Actor input Schema

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

ATP (men) and/or WTA (women). A tour with nothing in the date range simply contributes zero rows, not an error.

## `draws` (type: `array`):

Singles and/or doubles. A doubles row carries both players of each pair under player\_a.athletes / player\_b.athletes.

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

Which matches to return: all, live (in progress), upcoming (not started) or finished. Rows are always sorted live first, then upcoming, then finished.

## `dateFrom` (type: `string`):

First day to include, UTC. Defaults to today. A match is kept only when its own scheduled day falls in this range, not just its tournament's week.

## `dateTo` (type: `string`):

Last day to include, UTC. Defaults to dateFrom (or today). Capped at 31 days after dateFrom.

## `includeKalshi` (type: `boolean`):

Join each match to Kalshi's ATP/WTA moneyline market by player surname + date, when one exists. Never guessed: no confident match means kalshi is null.

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

Keep this many match rows (live first, then upcoming, then finished). Each row is one `match` event ($0.001).

## Actor input object example

```json
{
  "tours": [
    "ATP",
    "WTA"
  ],
  "draws": [
    "singles"
  ],
  "status": "all",
  "dateFrom": "",
  "dateTo": "",
  "includeKalshi": true,
  "maxMatches": 200
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("rowfeed/tennis-scores-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("rowfeed/tennis-scores-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 '{}' |
apify call rowfeed/tennis-scores-results-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rowfeed/tennis-scores-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/us3soU7g5ICc7iKMU/builds/NZcJCk6eGBesthTBp/openapi.json
