# Tennis Scraper — Live Scores, Results, Stats & Odds (`astravalabs/tennis-scraper`) Actor

Tennis matches from SofaScore for any date range, player or match: scores with set and tiebreak detail, match statistics, betting odds and point-by-point, across ATP, WTA, Challenger and ITF. Pay per match.

- **URL**: https://apify.com/astravalabs/tennis-scraper.md
- **Developed by:** [astravalabs](https://apify.com/astravalabs) (community)
- **Categories:** Sports, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 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 Scraper — Live Scores, Results, Stats & Odds

Tennis match data for any date range, player or match: final and live scores
with set and tiebreak detail, match statistics, betting odds and
point-by-point. Covers ATP, WTA, Challenger, WTA 125, ITF and UTR. One clean
row per match, ready for spreadsheets, models and dashboards.

Use it to build betting and prediction models, track players and
tournaments, research serve and return patterns, or feed live scores into
your own app.

### Quick start

A week of main-tour results with everything:

```json
{
  "dateFrom": "2026-10-01",
  "dateTo": "2026-10-07",
  "matchStatus": "finished",
  "includePointByPoint": true
}
```

Today's schedule across every tour:

```json
{ "dateFrom": "today", "matchStatus": "upcoming", "tours": [] }
```

Matches in play right now:

```json
{ "matchStatus": "live" }
```

A player's last 50 matches:

```json
{
  "players": ["https://www.sofascore.com/tennis/player/sinner-jannik/206570"],
  "maxMatchesPerPlayer": 50
}
```

`players` and `matches` take SofaScore URLs or numeric IDs. Dates are
calendar days in UTC, up to 31 per run.

### What you get

One row per match (real data, abridged):

```json
{
  "matchId": 17257382,
  "url": "https://www.sofascore.com/tennis/match/aleksandar-kovacevic-matteo-berrettini/IfVsicAb#id:17257382",
  "startTime": "2026-10-07T04:00:00+00:00",
  "status": "finished",
  "statusDescription": "Ended",
  "tour": "ATP",
  "tournament": "Shanghai",
  "tournamentPoints": 1000,
  "round": "Round of 128",
  "surface": "Hardcourt outdoor",
  "matchType": "singles",
  "city": "Shanghai",
  "player1": {"name": "Aleksandar Kovačević", "id": 187608, "country": "USA", "ranking": 81, "seed": null},
  "player2": {"name": "Matteo Berrettini", "id": 112783, "country": "ITA", "ranking": 46, "seed": null},
  "winner": "player2",
  "winnerName": "Matteo Berrettini",
  "setsPlayer1": 1,
  "setsPlayer2": 2,
  "score": "7-6(3) 1-6 4-6",
  "sets": [
    {"set": 1, "player1": 7, "player2": 6, "tiebreakPlayer1": 7, "tiebreakPlayer2": 3, "durationSeconds": 3527}
  ],
  "durationSeconds": 8124,
  "oddsPlayer1": 2.63,
  "oddsPlayer2": 1.5,
  "odds": [
    {"market": "Full time", "line": null, "isLive": false, "suspended": false,
     "choices": [
       {"name": "player1", "decimal": 2.63, "openingDecimal": 3.0, "winning": false},
       {"name": "player2", "decimal": 1.5, "openingDecimal": 1.4, "winning": true}]}
  ],
  "statistics": {
    "aces": {"player1": 3, "player2": 23},
    "firstServeAccuracy": {"player1": 64, "player2": 47, "player1Total": 111, "player2Total": 73},
    "breakPointsScored": {"player1": 0, "player2": 3}
  },
  "pointByPoint": [
    {"set": 1, "game": 1, "server": "player1", "winner": "player1", "tiebreak": false,
     "break": false, "gamesAfter": "1-0", "points": ["15-0", "15-15", "30-15", "40-15"]}
  ],
  "collectedAt": "2026-10-08T20:33:00+00:00"
}
```

- **Statistics:** about 20 items per match, keyed by stable names: aces,
  double faults, first and second serve in and won, return points, break
  points, games won, tiebreaks and more. Ratios keep both counts
  (`player1` made of `player1Total`).
- **Odds:** match winner, first set winner and total games, as decimal odds
  with opening prices. Each choice is flagged `winning` once settled.
- **Point-by-point:** every game in order with server, winner, breaks and
  the point sequence. Tiebreak games are marked and never counted as breaks.
- **Doubles:** each side also lists its two `players`.

### Options

| Field | Default | What it does |
|---|---|---|
| `dateFrom` / `dateTo` | today | Days to collect (UTC). `today`, `yesterday` and `tomorrow` work too. |
| `tours` | ATP, WTA, Challenger, WTA 125 | Add ITF Men/Women or UTR Men/Women, or leave empty for every tour. |
| `matchStatus` | all | `finished`, `upcoming` or `live` (live ignores the dates). |
| `matchType` | all | `singles` or `doubles`. |
| `players` | — | Player URLs or IDs; returns their most recent matches. |
| `maxMatchesPerPlayer` | 30 | Matches per player. |
| `matches` | — | Specific match URLs or IDs. |
| `includeStatistics` | true | Match statistics for played matches. |
| `includeOdds` | true | Betting odds. |
| `includePointByPoint` | false | Every game and point. Larger rows. |
| `maxMatches` | 1000 | Whole-run limit on matches saved (and charged). |

### Pricing

**$3.50 per 1,000 matches** ($0.0035 each), with statistics, odds and
point-by-point included at no extra cost, plus Apify's small Actor-start fee.
You pay only for matches saved. Matches SofaScore doesn't have, and matches
your filters exclude, are not charged.

### Coverage and reliability

Measured on 688 matches over seven days of ATP, WTA, Challenger and WTA 125
(October 2026):

- **Every match collected** (688 of 688), in about three minutes.
- **Statistics on 97%, point-by-point on 95%, odds on 93%.** The rest are
  walkovers, retirements, cancellations and smaller events where the source
  has no such data. Those fields are `null`, never guessed.
- **Cross-checked:** the winner agrees with sets won on every finished
  match.

ITF and UTR matches have results and usually statistics, but odds are
patchier at those levels.

### Run report

The `RUN_REPORT` record in the run's key-value store lists matches per day
and tour, each match's outcome, and any detail that was unavailable for a
match.

### Notes

- Data comes from SofaScore's public match pages. This Actor is independent
  and not affiliated with SofaScore, the ATP, the WTA or the ITF.
- Player 1 is the first-listed player on SofaScore.
- Rankings in a row are the players' rankings at the time of collection.

# Actor input Schema

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

First day (UTC) as YYYY-MM-DD, or today / yesterday / tomorrow. Empty means today. Used when no players or matches are given, or whenever you set it.

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

Last day (UTC), inclusive. Empty means the same as From date. Up to 31 days per run.

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

Which tours to include for a date range. Leave empty for every tour SofaScore lists that day.

## `matchStatus` (type: `string`):

Live now ignores the dates and returns the matches in play.

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

Keep singles, doubles or both.

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

SofaScore player URLs or IDs, e.g. https://www.sofascore.com/tennis/player/sinner-jannik/206570. Returns each player's most recent matches.

## `maxMatchesPerPlayer` (type: `integer`):

Most recent matches to collect per player.

## `matches` (type: `array`):

SofaScore match URLs or IDs, for specific matches.

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

Aces, double faults, serve and return percentages, break points and more, for played matches.

## `includeOdds` (type: `boolean`):

Match winner, first set winner and total games markets, current and opening prices as decimals.

## `includePointByPoint` (type: `boolean`):

Every game with server, winner, breaks and the point sequence. Makes rows larger.

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

Whole-run limit on matches saved (and charged).

## Actor input object example

```json
{
  "dateFrom": "yesterday",
  "tours": [
    "ATP",
    "WTA",
    "Challenger",
    "WTA 125"
  ],
  "matchStatus": "all",
  "matchType": "all",
  "maxMatchesPerPlayer": 30,
  "includeStatistics": true,
  "includeOdds": true,
  "includePointByPoint": false,
  "maxMatches": 1000
}
```

# Actor output Schema

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

The dataset: one row per match with players, score, sets, statistics, odds and point-by-point.

## `runReport` (type: `string`):

Per-match outcome, matches per day and tour, which details were unavailable, and request counts.

# 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 = {
    "dateFrom": "yesterday"
};

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

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

```

## MCP server setup

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