# Tennis Scraper: Scores, Rankings, Stats & Odds (160+ Books) (`zayne21/tennis-scores-stats-odds`) Actor

Every ATP, WTA, Challenger and ITF match (singles and doubles): live scores, set-by-set results with tiebreaks, retirements, ATP/WTA rankings, match statistics and odds from 160+ bookmakers with fair odds, value and arbitrage. No browser, fast and cheap.

- **URL**: https://apify.com/zayne21/tennis-scores-stats-odds.md
- **Developed by:** [Elie Rusak](https://apify.com/zayne21) (community)
- **Categories:** Sports
- **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

## Tennis Scraper: Scores, Rankings, Stats & Odds (160+ Books)

Get **every ATP, WTA, Challenger and ITF match** — singles and doubles — for the days you choose, with:

- **Live scores and results**: set-by-set games, **tiebreak points**, sets won, winner, retirements, walkovers, cancellations, start and end time.
- **Tournament details**: tour, tournament, country, **surface**, indoor, qualification.
- **ATP / WTA rankings**: current singles rank and points of both players (free).
- **Match statistics** (optional): aces, double faults, first serve %, serve and return points won, break points saved and converted, games won... for the match and for each set.
- **Odds from 160+ bookmakers in 40+ countries** (optional): match winner, set winner, set and game handicaps, total sets, total games, correct score, odd/even — with best price, opening odds, margins, **margin-free fair odds**, **value bets** and **arbitrage (surebets)**.

The Actor reads FlashScore's data feeds directly — **no browser**: a whole day of tennis (150+ matches) takes about 10 seconds.

### What can you do with it?

- **Live score and results feeds** for apps, bots, dashboards and websites.
- **Tennis models and backtests**: results with rankings, statistics and closing odds of the last 7 days, or schedule the Actor daily to build your own history.
- **Odds comparison and value betting research**: best price per outcome across all bookmakers of your country, compared with the fair odds of the whole market.
- **Arbitrage scanning** on upcoming matches.

### Input

| Field | Description |
|---|---|
| Days ahead / Past days | Today up to 7 days ahead, and up to 7 days back. |
| Tours | ATP, WTA, Challenger (men/women), ITF (men/women), Other (team events, juniors, exhibitions). Empty = all. |
| Singles / doubles | Empty = both. |
| Match statuses | Upcoming, live, finished (finished includes retirements, walkovers and cancellations). |
| Tournaments | Optional: part of the name or FlashScore URL (`Shanghai`, `/tennis/wta-singles/wuhan/`). |
| Players | Optional: part of a player's name, accents ignored (`Sinner`, `Swiatek`). |
| Maximum matches | Cost cap (earliest matches first). |
| Include rankings / statistics / odds | Enrichments, see pricing. |
| Bookmaker countries, markets, periods | Which bookmakers and markets to compare when odds are included. |

Example — today's and tomorrow's ATP and WTA singles with statistics and UK + French odds:

```json
{
  "daysAhead": 1,
  "tours": ["ATP", "WTA"],
  "matchTypes": ["singles"],
  "includeStatistics": true,
  "includeOdds": true,
  "countries": ["GB", "FR"]
}
```

### Output

One item per match (real run, shortened):

```json
{
  "matchId": "xUS4LN7s",
  "url": "https://www.flashscore.com/match/tennis/munar-jaume-bgno6t9s/jacquet-kyrian-W6pg4Kwf/?mid=xUS4LN7s",
  "kickoff": "2026-10-04T02:10:00Z",
  "endTime": "2026-10-04T03:50:36Z",
  "status": "finished",
  "tour": "ATP",
  "matchType": "singles",
  "competition": "ATP - SINGLES: Tokyo (Japan), hard",
  "tournament": "Tokyo",
  "tournamentCountry": "Japan",
  "surface": "hard",
  "indoor": false,
  "qualification": false,
  "homePlayer": "Munar J.",
  "awayPlayer": "Jacquet K.",
  "homePlayers": [{"name": "Munar J.", "playerId": "zZieQm4D", "country": "Spain",
                   "url": "https://www.flashscore.com/player/munar-jaume/zZieQm4D/"}],
  "homeSets": 2,
  "awaySets": 0,
  "sets": [{"home": 6, "away": 4}, {"home": 6, "away": 2}],
  "winner": "home",
  "homeRank": 49, "homeRankingPoints": 1100,
  "awayRank": 76, "awayRankingPoints": 781,
  "bestHome": 1.67, "bestBookmakerHome": "bet365", "fairHome": 1.727,
  "bestAway": 2.3, "bestBookmakerAway": "William Hill", "fairAway": 2.375,
  "winnerMarginPercent": 6.62,
  "bookmakerCount": 20,
  "statistics": {
    "match": {
      "aces": {"home": 3, "away": 1},
      "double_faults": {"home": 2, "away": 1},
      "first_serve_percentage": {"home": "60%", "away": "52%"},
      "first_serve_points_won": {"home": "67% (26/39)", "away": "59% (20/34)"}
    },
    "set1": {"aces": {"home": 1, "away": 0}}
  },
  "odds": {
    "countries": ["GB"],
    "valueBets": [],
    "arbitrages": [],
    "markets": [
      {
        "market": "winner", "period": "match", "line": null,
        "outcomes": [
          {"outcome": "home", "bestOdds": 1.67, "bestBookmaker": "bet365", "fairOdds": 1.727,
           "fairProbability": 0.5789, "valuePercent": -3.32, "outlier": false,
           "odds": {"10bet": 1.6, "7BetUK": 1.66, "bet365": 1.67},
           "openingOdds": {"10bet": 1.55, "7BetUK": 1.57, "bet365": 1.62}}
        ],
        "fairOddsBookmakerCount": 20,
        "averageMarginPercent": 6.62,
        "lowestMarginBookmaker": "Betway",
        "arbitrage": null
      }
    ]
  },
  "scrapedAt": "2026-10-05T21:47:54Z"
}
```

Tiebreaks appear as `tiebreakHome` / `tiebreakAway` in the set they decided (`{"home": 7, "away": 6, "tiebreakHome": 7, "tiebreakAway": 4}`). Doubles list both players of each team in `homePlayers` / `awayPlayers`. Handicap lines are given from the first player's side (`line: -1.5` = player 1 -1.5 / player 2 +1.5). Rankings are the current ones, not those at the time of past matches.

### How fair odds, value and arbitrage are computed

1. For each bookmaker that prices both outcomes of a line, its margin is removed (proportional method by default, or the power method).
2. The fair probability is the **median over bookmakers**, normalised to 100 %. Fair odds = 1 / fair probability.
3. **Value %** = best price × fair probability − 1, computed when at least 3 bookmakers price the line.
4. **Arbitrage** is reported when the best prices of both outcomes add up to less than 100 %, with the stake share for each.
5. A best price more than 25 % above fair odds is marked `outlier` (stale price or palpable error) and is never flagged.
6. Value bets and arbitrage are flagged for **upcoming matches only**. For live and finished matches you get each bookmaker's last price (closing odds), taken at slightly different moments.

### Pricing

Pay per result, no subscription:

| Event | When |
|---|---|
| Match | each match saved (scores, players, tournament, rankings included) |
| Odds | each match saved with bookmaker odds (only when *Include bookmaker odds* is on) |
| Statistics | each match saved with statistics (only when *Include match statistics* is on) |

Matches skipped with *Only matches with odds* are free. Set *Maximum matches* to cap the cost of a run.

### Tips

- Upcoming start times are FlashScore's schedule; matches later in a day's order of play often start after the time shown.
- Odds markets per set (set 1, set 2…) are only offered by some bookmakers.
- Schedule the Actor (for example every 15 minutes during play) and use webhooks or the API for live data.

### FAQ

**Is it legal to scrape this data?** The Actor collects publicly visible scores, statistics and prices, with no personal data beyond players' public names. Check the terms of the source website and your local laws for your use case.

**Is this betting advice?** No. Value and arbitrage flags are price comparisons against the market consensus. Bookmakers can change prices, void obvious errors and limit accounts. Gamble responsibly — 18+ only. UK: GamCare 0808 8020 133.

**Is this Actor affiliated with FlashScore, the ATP, the WTA or the bookmakers?** No.

**Something is missing or broken?** Open an issue in the Issues tab — fixes are usually quick.

# Actor input Schema

## `daysAhead` (type: `integer`):

Matches from today up to this many days ahead (0 = today only, max 7).

## `daysBack` (type: `integer`):

Also include matches of the last N days (max 7), with final scores.

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

Leave empty for all tours.

## `matchTypes` (type: `array`):

Leave empty for both.

## `statuses` (type: `array`):

Upcoming, live and/or finished (finished includes retirements, walkovers and cancellations).

## `tournaments` (type: `array`):

Optional. Keep only competitions whose name or FlashScore URL contains one of these, e.g. Beijing, Roland Garros, /tennis/atp-singles/shanghai/.

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

Optional. Keep only matches of these players (part of the name, accents ignored), e.g. Sinner, Swiatek.

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

Stop after saving this many matches (earliest first). You are charged per match saved.

## `includeRankings` (type: `boolean`):

Current singles ranking and points of each player (free).

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

Aces, double faults, serve and return points, break points, games won... for the match and each set (live and finished matches, small extra charge per match).

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

Prices of all bookmakers of the selected countries, best price, fair odds, value bets and arbitrage (extra charge per match with odds).

## `countries` (type: `array`):

Bookmakers licensed in these countries are compared (UK: 20 bookmakers, Italy: 13, Canada: 10, France: 8...). Several countries are merged into one comparison per match.

## `markets` (type: `array`):

Markets collected when odds are included.

## `periods` (type: `array`):

Whole match and/or individual sets.

## `onlyMatchesWithOdds` (type: `boolean`):

When odds are included, skip matches no bookmaker of the selected countries prices (not charged).

## `valueThresholdPercent` (type: `integer`):

An outcome is flagged as a value bet when best price x fair probability - 1 is at least this percentage.

## `valueMaxOdds` (type: `integer`):

Ignore value bets above these odds.

## `demarginMethod` (type: `string`):

How each bookmaker's margin is removed before taking the median over bookmakers.

## `includeOpeningOdds` (type: `boolean`):

Add each bookmaker's opening price.

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

Not needed in most cases.

## Actor input object example

```json
{
  "daysAhead": 0,
  "daysBack": 0,
  "tours": [
    "ATP",
    "WTA"
  ],
  "statuses": [
    "scheduled",
    "live",
    "finished"
  ],
  "tournaments": [
    "Shanghai",
    "Wuhan"
  ],
  "players": [
    "Sinner",
    "Alcaraz"
  ],
  "maxMatches": 30,
  "includeRankings": true,
  "includeStatistics": false,
  "includeOdds": false,
  "countries": [
    "GB"
  ],
  "markets": [
    "winner",
    "set_handicap",
    "game_handicap",
    "total_sets",
    "total_games"
  ],
  "periods": [
    "match"
  ],
  "onlyMatchesWithOdds": false,
  "valueThresholdPercent": 2,
  "valueMaxOdds": 10,
  "demarginMethod": "proportional",
  "includeOpeningOdds": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `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 = {
    "tours": [
        "ATP",
        "WTA"
    ],
    "maxMatches": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("zayne21/tennis-scores-stats-odds").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 = {
    "tours": [
        "ATP",
        "WTA",
    ],
    "maxMatches": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("zayne21/tennis-scores-stats-odds").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 '{
  "tours": [
    "ATP",
    "WTA"
  ],
  "maxMatches": 30
}' |
apify call zayne21/tennis-scores-stats-odds --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zayne21/tennis-scores-stats-odds"
        }
    }
}
```

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/voxw12ZmIrLVLrppQ/builds/aQo1vQ5jUcJZSYF68/openapi.json
