# Tennis Scraper (`parsebird/tennis-scraper`) Actor

All-in-one tennis data API: live scores, tournament schedules, point-by-point, seasons, and player rankings with Elo ratings from SofaScore and Tennis Abstract.

- **URL**: https://apify.com/parsebird/tennis-scraper.md
- **Developed by:** [ParseBird](https://apify.com/parsebird) (community)
- **Categories:** Developer tools, Integrations, Other
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.89 / 1,000 record scrapeds

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/platform/actors/running/actors-in-store#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

### Tennis Scraper

Scrape live tennis scores, tournament schedules, point-by-point match data, and player rankings with Elo ratings — one all-in-one **tennis scraper API** combining [SofaScore](https://www.sofascore.com/) and [Tennis Abstract](https://www.tennisabstract.com/).

<table><tr>
<td style="border-left:4px solid #1C1917;padding:12px 16px;font-weight:600">
Six modes in a single Actor: live ATP/WTA scores with set-by-set breakdowns and odds, tournament discovery by date, point-by-point progression, tournament seasons and prize-money/chronology details, and player profiles combining SofaScore rankings with Tennis Abstract Elo ratings and recent-match stats.
</td>
</tr></table>

##### Copy to your AI assistant

Copy this block into ChatGPT, Claude, Cursor, or any LLM to start using this actor.

```
parsebird/tennis-scraper on Apify. Call: ApifyClient("TOKEN").actor("parsebird/tennis-scraper").call(run_input={...}), then client.dataset(run["defaultDatasetId"]).list_items().items for results. Required input: mode (string enum: "liveMatches", "tournaments", "pointByPoint", "tournamentSeasons", "tournamentDetails", "playerDetails"; default "liveMatches"). Mode-specific inputs (fields for other modes are accepted and ignored): maxMatches (integer, liveMatches, 0=all live matches), date (string "YYYY-MM-DD", tournaments), matchId (positive integer, pointByPoint — get from liveMatches/tournamentDetails results), tournamentId (positive integer, tournamentSeasons/tournamentDetails — get from tournaments/liveMatches results), seasonId (positive integer, tournamentDetails, optional — get from tournamentSeasons, omit for newest season), playerId (positive integer, playerDetails — get from match participant IDs in liveMatches/tournamentDetails results). Output fields vary by mode: liveMatches returns id/slug/status/tournamentName/homePlayerName/awayPlayerName/score(home,away,sets[])/odds/url; tournaments returns tournamentId/tournamentName/category/groundType/date; pointByPoint returns matchId/sets[](games[](points[])); tournamentSeasons returns seasonId/name/year; tournamentDetails returns seasonId/hostCity/numberOfCompetitors/totalPrizeMoney/matches[]; playerDetails returns fullName/country/handedness/currentRank/eloRank/eloRating/recentResults[](dominanceRatio,acePercentage). Full actor spec: fetch build via GET https://api.apify.com/v2/acts/parsebird~tennis-scraper (Bearer TOKEN). Get token: https://console.apify.com/account/integrations
```

### What does Tennis Scraper do?

This Actor is an all-in-one **tennis data API** — it replaces separately maintaining a SofaScore live-score scraper and a Tennis Abstract stats scraper with one Actor and one `mode` parameter. It renders SofaScore's internal API through a real browser (SofaScore blocks plain HTTP requests at the network-fingerprint level) and combines it with Tennis Abstract's public Elo ratings and match-history reports.

- 🎾 **Live ATP/WTA scores** — every match in progress right now, with set-by-set scores, serve side, and moneyline odds
- 📅 **Tournament discovery** — find every tournament (Grand Slam down to ITF) scheduled on any date
- 🏓 **Point-by-point data** — full game-by-game, point-by-point progression for a specific match
- 🏆 **Tournament seasons & details** — every historical season of a tournament, plus host city, prize money, competitor count, and match chronology for a given season
- 👤 **Player profiles** — ranking, handedness, height, turned-pro year, prize money, **Elo/hElo/cElo/gElo ratings** from Tennis Abstract, and recent match results with dominance ratio and ace percentage
- ⏱️ **Scheduling** — run any mode on a timer via Apify Scheduler for live monitoring or daily digests
- 🔌 **API access** — trigger runs and pull results programmatically via the Apify API or client libraries
- 📊 **Flexible export** — download results as JSON, CSV, or Excel, or query them via API

### What data can you extract from SofaScore and Tennis Abstract?

| Field | Available in | Description |
|-------|--------------|-------------|
| `homePlayerName` / `awayPlayerName` / `score.sets` | liveMatches | Live match participants and set-by-set score |
| `odds` | liveMatches | Moneyline odds (fractional and decimal) |
| `tournamentName` / `tournamentId` / `category` | tournaments | Tournament identity and tour (ATP/WTA/ITF) |
| `sets[].games[].points[]` | pointByPoint | Point-by-point progression within each game and set |
| `seasonId` / `name` / `year` | tournamentSeasons | Every historical season of a tournament |
| `hostCity` / `numberOfCompetitors` / `totalPrizeMoney` / `matches` | tournamentDetails | Season metadata and match chronology |
| `currentRank` / `handedness` / `eloRank` / `eloRating` | playerDetails | SofaScore ranking plus Tennis Abstract Elo rating |
| `recentResults[].dominanceRatio` / `.acePercentage` | playerDetails | Serve/return dominance and ace rate per recent match |

### Input parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `mode` | string | **Yes** | `liveMatches` | `liveMatches`, `tournaments`, `pointByPoint`, `tournamentSeasons`, `tournamentDetails`, or `playerDetails` |
| `maxMatches` | integer | No | `0` (all) | Cap on results for `liveMatches` |
| `date` | string | For `tournaments` | — | `YYYY-MM-DD` date to discover tournaments |
| `matchId` | integer | For `pointByPoint` | — | SofaScore match ID |
| `tournamentId` | integer | For `tournamentSeasons`/`tournamentDetails` | — | SofaScore tournament ID |
| `seasonId` | integer | No | newest | Season ID for `tournamentDetails` |
| `playerId` | integer | For `playerDetails` | — | SofaScore player ID |
| `proxy` | object | No | Apify residential proxy | Connection settings |

Fields not used by the selected `mode` are accepted and silently ignored — you can leave one full input object configured and just switch `mode` between runs.

### Output example

**Live match** (`mode: "liveMatches"`)

```json
{
  "id": 16385412,
  "slug": "coco-gauff-karolina-muchova",
  "status": "3rd set",
  "tournamentName": "Wimbledon",
  "homePlayerName": "Karolina Muchova",
  "awayPlayerName": "Coco Gauff",
  "score": {
    "home": 2,
    "away": 1,
    "sets": [
      { "set": 1, "home": 6, "away": 2, "durationSeconds": 2357 },
      { "set": 2, "home": 1, "away": 6, "durationSeconds": 2127 }
    ]
  },
  "odds": {
    "home": { "fractional": "3/1", "decimal": 4.0 },
    "away": { "fractional": "2/9", "decimal": 1.22 }
  }
}
```

**Player profile** (`mode: "playerDetails"`)

```json
{
  "id": 206570,
  "fullName": "Jannik Sinner",
  "country": "Italy",
  "handedness": "Right-handed",
  "currentRank": 1,
  "eloRank": 1,
  "eloRating": 2331.9,
  "recentResults": [
    {
      "date": "2026-06-29",
      "tournament": "Wimbledon",
      "surface": "Grass",
      "result": "W",
      "opponentName": "Alexander Zverev",
      "score": "6-7(7) 7-6(2) 6-3 6-4",
      "dominanceRatio": 1.237,
      "acePercentage": 11.0
    }
  ]
}
```

Download results in **JSON, CSV, or Excel** directly from the Apify Console, or pull them programmatically via the API.

### Use cases

- **Live-score widgets and dashboards** — poll `liveMatches` on a schedule to power a live tennis scoreboard
- **Odds monitoring** — track moneyline odds movement across live matches for betting or trading analysis
- **Tournament coverage** — pull `tournaments` for a date to build a daily "what's on" digest across ATP, WTA, and ITF events
- **Match analysis** — use `pointByPoint` to reconstruct momentum swings, break-point sequences, and serve patterns for a specific match
- **Player research and scouting** — combine `playerDetails`' SofaScore ranking with Tennis Abstract Elo and recent-match dominance ratio for form analysis
- **Historical research** — walk `tournamentSeasons` and `tournamentDetails` to build a season-by-season archive of a tournament's champions and results

### How to use Tennis Scraper

1. Go to the [Tennis Scraper](https://apify.com/parsebird/tennis-scraper) page on Apify Store
2. Click **Try for free** — new users get a free trial to test the Actor before paying
3. Pick a **mode** from the dropdown — the form preselects `liveMatches`
4. Fill in the field that mode needs (`date` for tournaments, `matchId` for point-by-point, `tournamentId`/`seasonId` for tournament details, `playerId` for player details) — get IDs by chaining from a previous run's results
5. Click **Start** — the Actor renders SofaScore's own API in a real browser and enriches player data with Tennis Abstract's Elo ratings
6. When the run finishes, download your data as **JSON, CSV, or Excel**, or fetch it via the API

### Use with the Apify API

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")

run = client.actor("parsebird/tennis-scraper").call(run_input={
    "mode": "liveMatches",
    "maxMatches": 20,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"{item['homePlayerName']} vs {item['awayPlayerName']} — {item['status']}")
```

**JavaScript**

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

const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });

const run = await client.actor('parsebird/tennis-scraper').call({
    mode: 'playerDetails',
    playerId: 206570,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.log(`${item.fullName} — ATP #${item.currentRank}, Elo rank #${item.eloRank}`);
});
```

See the [Apify API documentation](https://docs.apify.com/api/v2) for full reference, or the Actor's **API** tab for ready-to-run request snippets in more languages. Both `mode: "liveMatches"` and `mode: "tournaments"` return multiple dataset rows in one run; `pointByPoint`, `tournamentDetails`, and `playerDetails` return one row with nested detail.

### How much does it cost to scrape tennis data?

This Actor uses **Pay-per-event** pricing — you only pay for records actually returned, with no separate platform rental fee.

| Event | Price per event (Free plan) | Price per 1,000 |
|-------|------------------------------|------------------|
| `record-scraped` | $0.00699 | $6.99 |

`record-scraped` is charged once per dataset row: one live match, one tournament, the point-by-point match record, one season, one tournament-details record, or one player-details record. Paid plans get a lower per-event rate ($6.79/1,000 on Bronze, $6.49/1,000 on Silver, $5.89/1,000 on Gold). Start with the free trial and a low `maxMatches` to preview results before scaling up.

### FAQ

**Why does this Actor need a browser instead of a simple API call?**
SofaScore blocks non-browser HTTP clients at the network-fingerprint level — the same request that returns real data from a browser returns a 403 from a plain HTTP library. This Actor renders every SofaScore request in a real headless browser, pinned to a country-specific residential proxy, to get through reliably.

**Where do the Elo ratings come from?**
SofaScore doesn't publish Elo ratings. `playerDetails` cross-references [Tennis Abstract's](https://www.tennisabstract.com/) publicly published ATP/WTA Elo ratings report by player name to attach `eloRank`, `eloRating`, and peak-Elo fields alongside the SofaScore profile.

**Why is `recentResults` sometimes shorter than expected, or empty?**
Recent match history comes from Tennis Abstract's own per-player match archive. It only covers tour-level ATP/WTA matches with complete stats recorded — very new pros, retired players, or players outside Tennis Abstract's coverage may return an empty `recentResults` array while the rest of the profile still populates from SofaScore.

**How do I get a `playerId`, `matchId`, or `tournamentId`?**
Chain them from other modes: `liveMatches` and `tournamentDetails` return match participant IDs (use as `playerId`) and match IDs (use as `matchId`); `tournaments` and `liveMatches` return `tournamentId`; `tournamentSeasons` returns `seasonId`. The default input example includes real, working sample IDs (Wimbledon, a real Wimbledon match, and Jannik Sinner) to try each mode immediately.

**What happens if I set fields for a mode I'm not using?**
They're accepted and ignored. Only the field(s) required by the selected `mode` are validated when the run starts — you can keep one input object configured with every field filled in and just change `mode` between runs.

**Can I schedule recurring runs?**
Yes. Use Apify's [Scheduler](https://docs.apify.com/platform/schedules) to run `liveMatches` every few minutes for live monitoring, or `tournaments` daily for a fixtures digest.

**Can I access results via API?**
Yes. Every run's dataset is available through the [Apify API](https://docs.apify.com/api/v2) and client libraries (Python, JavaScript) — see the Actor's **API** tab for ready-made snippets, and [Apify's integrations](https://apify.com/integrations) for Zapier, Make, and Google Sheets connections.

**Is it legal to scrape tennis data with this Actor?**
Yes, for publicly available data. Both SofaScore and Tennis Abstract publish their match, tournament, and player data on pages that don't require a login. You're responsible for complying with each site's Terms of Service for your specific use case — see Apify's [blog post on the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) for general background.

**Something not working?**
Open an issue on the Actor's **Issues** tab in Apify Console — the ParseBird team monitors it directly.

### Related Actors

More sports and monitoring Actors from ParseBird:

- [SofaScore Scraper](https://apify.com/parsebird/sofascore-scraper) — live scores, lineups, statistics, and odds across every sport SofaScore covers, not just tennis
- [Tennis Abstract Player Match Scraper](https://apify.com/parsebird/tennis-abstract-match-scraper) — a player's full ATP/WTA match history with per-match serve/return stats, without the SofaScore live-score layer
- [HTTP Request Actor](https://apify.com/parsebird/http-request-actor) — general-purpose API/webhook integration

# Actor input Schema

## `mode` (type: `string`):

Select the data operation. Known fields from another mode are accepted by the runtime and ignored; mode-specific requirements are checked when the run starts.

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

Used only when Mode is liveMatches. Optional non-negative cap; omit it (or use 0) to return all current live matches.

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

Used only when Mode is tournaments. A real calendar date in YYYY-MM-DD format is required; discovers tournaments scheduled on that date.

## `matchId` (type: `integer`):

Used only when Mode is pointByPoint. A positive match ID is required; get it from a match returned by liveMatches or tournamentDetails.

## `tournamentId` (type: `integer`):

Used only when Mode is tournamentSeasons or tournamentDetails. A positive tournament ID is required; get it from tournaments or liveMatches.

## `seasonId` (type: `integer`):

Used only when Mode is tournamentDetails. Optional positive season ID; get it from tournamentSeasons, or omit it to resolve the newest season.

## `playerId` (type: `integer`):

Used only when Mode is playerDetails. A positive player ID is required; get it from match participants returned by liveMatches or tournamentDetails.

## `proxy` (type: `object`):

SofaScore blocks both non-browser traffic and datacenter IPs even from a real browser; a country-pinned residential proxy is required for reliable access.

## Actor input object example

```json
{
  "mode": "liveMatches",
  "maxMatches": 10,
  "date": "2026-07-10",
  "matchId": 16385412,
  "tournamentId": 2361,
  "seasonId": 63966,
  "playerId": 206570,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "mode": "liveMatches",
    "maxMatches": 10,
    "date": "2026-07-10",
    "matchId": 16385412,
    "tournamentId": 2361,
    "seasonId": 63966,
    "playerId": 206570,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("parsebird/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 = {
    "mode": "liveMatches",
    "maxMatches": 10,
    "date": "2026-07-10",
    "matchId": 16385412,
    "tournamentId": 2361,
    "seasonId": 63966,
    "playerId": 206570,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("parsebird/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 '{
  "mode": "liveMatches",
  "maxMatches": 10,
  "date": "2026-07-10",
  "matchId": 16385412,
  "tournamentId": 2361,
  "seasonId": 63966,
  "playerId": 206570,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call parsebird/tennis-scraper --silent --output-dataset

```

## MCP server setup

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