# League Standings & Top Scorers Scraper — ATP, WTA (`scapypuppy/league-standings-top-scorers`) Actor

League tables, season scoring charts and tennis rankings in one Actor: rank, form, goals, points, qualification zones, next fixture, top scorers with club and nationality, ATP and WTA rankings with points and position change, tennis Elo by surface. No login, no browser.

- **URL**: https://apify.com/scapypuppy/league-standings-top-scorers.md
- **Developed by:** [Huyen Trang](https://apify.com/scapypuppy) (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 league processeds

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

## League Standings & Top Scorers Scraper — ATP, WTA

Paste a league page and get **the full table and the season scoring chart**: rank, played, wins, draws, losses, goals, points, points per game, recent form, qualification zone and each team's next fixture — plus top scorers with club, position and nationality.

Paste a ranking page instead and get **ATP or WTA rankings** with points, position and how far each player moved since the last update. Flip one switch and get **tennis Elo ratings** on top — overall and per surface — from Tennis Abstract.

**No login. No cookies. No API key. No headless browser.**

### Quick start

1. In **League pages**, pick leagues from the list or paste any Flashscore league page. In
   **Ranking pages**, pick ATP or WTA tables from the list; leave either field empty if you do
   not need it.
2. Press **Start**. The form as first opened — the Premier League and LaLiga with 50 top
   scorers each, the ATP top 10 — comes to $0.02265 (see **What a run costs**).
3. Open the **Output** tab. **League tables** has one row per team, **Top scorers** one row
   per player, **Rankings** one row per ranking position; **Leagues** lists the league records
   with their row counts and any error.
4. For a spreadsheet, press **Export**, set **View** to **League tables** (or **Top scorers**)
   and the format to **Excel**, then **Download**. Export covers the league records only: for
   the rankings open the run's **Storage** tab, pick the rankings dataset and export it there.
   Details in **Download as a spreadsheet**.

### What you get

| Capability | What it means for you |
|---|---|
| **Season scoring chart** | player, club and club code, goals, assists, position and nationality in the same run as the table; points too where the source publishes them (NHL) |
| **Home and away tables** | two extra tables built only from home or only from away matches |
| **Recent form per team** | a W/D/L string of the latest results, most recent first, and the current streak checked against the league's form table: `W6`, `L10+` |
| **Team logos** | a link to every team's logo in each table row, from the same feed, no extra request |
| **Next fixture per team** | opponent and the match date attached to every row — the source publishes the day, not the kickoff time |
| **Qualification zones** | which rows are Champions League, play-off or relegation — as the source's own label ("Promotion - Champions League"), plus the raw code and colour |
| **ATP and WTA rankings** | eight tables: singles, doubles and the Race of each, with points and position change |
| **Tennis Elo ratings** | overall Elo plus separate hard, clay and grass ratings for both tours, with peak Elo and the official rank next to it |
| **Conferences, divisions and groups** | NBA, NHL, MLB and Nations League rows carry their group, streak and the qualified-for mark; NBA and MLB rows also win percentage and games behind. A team is billed once per table, however many groups it appears in |
| **Form, Over/Under and HT/FT tables** | the extra tables of the league page, overall, home or away, billed like the main table; keep only the lines and form windows you need |
| **Past seasons** | `lastSeasons` and `seasons` return earlier seasons of the same league page, one league record per season |
| **Only what changed** | `onlyChanges` returns and bills only the rows that are new or changed since the previous run: made for daily schedules |
| **Joins with match data** | league, season, stage and team ids under the same names and values as in our match Actors |
| **A table view for each kind of row** | league tables, top scorers, home, away and extra tables, rankings; `outputLayout: rows` writes one record per row instead |
| **Any sport, any country** | football, basketball, hockey, handball — the same league page format |

### How this compares

| | This Actor | Other Store Actors (14 Oct 2026) |
|---|---|---|
| A 20-team league table | **$0.00645** | $0.008 on an ESPN standings Actor ($0.0004 a row) and on a SofaScore Actor that bills $0.008 a run for one league; $0.01005 on a Flashscore league archive Actor; $0.04 on an ESPN sports Actor |
| The same table with 50 top scorers | **$0.00695** | $0.008 on the SofaScore Actor above, whose run also carries 32 leader categories and the league's matches |
| NBA table, 30 teams in conferences and divisions | **$0.00915** | $0.012 on the ESPN standings Actor |
| ATP top 100 | **$0.07905** | $0.09 on a tennis rankings Actor; $0.20 on a Tennis Abstract Actor; $0.02 and $0.049 plus a start fee on two Actors with one or no users in the last 30 days |
| One Elo row | **$0.00078** | $0.00049 and $0.002 on two Tennis Abstract Actors |
| Top scorers | **yes**, with club, club code, position and nationality | $0.0002 a row on the Flashscore archive Actor; $0.005 a league on another Flashscore Actor |
| Home, away, form, over/under and HT/FT tables | **yes** | the Flashscore archive Actor returns the same table types |
| Proxies | **direct first, then a datacenter proxy**; no residential proxy needed | residential proxies built in or on by default on two SofaScore-based Actors |

Prices of other Actors are their Free-plan event prices on 14 October 2026, with the run
start where they charge one; the run totals of this Actor include its own $0.00005 start.

### Output example

One league record of the default nested layout, from a local test run on 14 October 2026
with the input form as first opened; the arrays are cut to their first item:

```json
{
  "leagueUrl": "https://www.flashscore.com/football/england/premier-league/",
  "league": "Premier League",
  "tournamentId": "dYlOSQOD",
  "tournamentSeasonId": "SY30SsKF",
  "tournamentStageId": "CfoA8Dmm",
  "season": "2026/2027",
  "stageName": "Main",
  "standings": [
    {
      "rank": 1, "team": "Manchester City", "teamId": "Wtn9Stg0",
      "teamUrl": "https://www.flashscore.com/team/manchester-city/Wtn9Stg0/",
      "teamLogoUrl": "https://static.flashscore.com/res/image/data/0vgscFU0-lQuhqN8N.png",
      "played": 6, "wins": 6, "draws": 0, "losses": 0,
      "goalsFor": 16, "goalsAgainst": 5, "goalDifference": 11, "points": 18, "pointsPerGame": 3,
      "form": "WWWWW", "zone": "q1", "zoneLabel": "Promotion - Champions League (League phase)",
      "zoneColor": "#004682", "nextOpponent": "Ipswich", "nextMatchDate": "2026-10-17",
      "group": null, "groupType": null, "winPercentage": null, "gamesBehind": null,
      "streak": "W6", "streakMin": 6,
      "winsRegulation": 6, "winsOvertime": 0, "winsPenalties": 0,
      "lossesRegulation": 0, "lossesOvertime": 0, "lossesPenalties": 0, "qualificationStatus": null
    }
  ],
  "homeTable": [],
  "awayTable": [],
  "extraTables": [],
  "topScorers": [
    {
      "rank": 1, "player": "Haaland E.", "playerId": "UmV9iQmE",
      "playerUrl": "https://www.flashscore.com/player/haaland-erling/UmV9iQmE/",
      "team": "Manchester City", "teamId": "Wtn9Stg0",
      "teamUrl": "https://www.flashscore.com/team/manchester-city/Wtn9Stg0/", "teamCode": "MCI",
      "goals": 6, "assists": 0, "points": null, "position": "Forward", "nationality": "Norway"
    }
  ],
  "rowsTotal": 70,
  "rowsReturned": 70,
  "rowsBilled": 70,
  "truncated": false,
  "scrapedAt": "2026-10-14T17:32:27.158Z"
}
```

A ranking record of the same run, in the run's `rankings` dataset:

```json
{
  "rankingUrl": "https://www.flashscore.com/tennis/rankings/atp/",
  "ranking": "ATP Rankings",
  "rankingDate": "2026-10-05",
  "rows": [
    {
      "rank": 1, "previousRank": 1, "rankChange": 0, "player": "Sinner Jannik", "playerId": "6HdC3z4H",
      "playerUrl": "https://www.flashscore.com/player/sinner-jannik/6HdC3z4H/", "country": "Italy",
      "points": 11000, "tournamentsPlayed": 16, "tied": false
    }
  ],
  "rowsTotal": 10,
  "rowsReturned": 10,
  "rowsBilled": 10,
  "truncated": false,
  "scrapedAt": "2026-10-14T17:32:27.186Z"
}
```

The dataset schema of the Actor gives every field a title, a description and, where a run
had one, an example value.

### Fields

**Table row** — rank, team, team id, page and logo (`teamLogoUrl`), played, wins, draws, losses, goals for,
goals against, goal difference, points, points per game, form (a W/D/L string of
the latest results, most recent first), qualification zone as a code, its label
from the source's legend and its colour, next opponent, next match date.
The next match date is a `YYYY-MM-DD` day: the standings feed does not carry
kickoff times. Rows also carry `group` and `groupType` (conference, division or group),
`winPercentage`, `gamesBehind`, `streak` (for example `W3`) with its length as a number
(`streakMin`, see **Conferences, divisions and groups**), regulation, overtime and
shoot-out wins and losses, and `qualificationStatus` once a place is secured. Rows of the
extra tables carry `teamLogoUrl` too.
`winPercentage` is a share from 0 to 1, `null` for a team that has not played yet: the source
writes zero there. In hockey `losses` counts every loss, overtime and shoot-out losses
included, as the source does; the NHL's own OTL column is `lossesOvertime` + `lossesPenalties`,
and regulation losses are `lossesRegulation`.

**League record** — besides the tables it carries `league`, `season`, `stageName`, the ids
`tournamentId` (the league), `tournamentSeasonId` (the season) and `tournamentStageId` (the
stage whose table is returned), and `extraTables` when you ask for them.

**Every league, ranking and Elo record** of the nested layout also carries `rowsReturned`
(the rows it holds: table rows plus top scorers for a league), `rowsBilled` (the billed ones:
a team counts once per table, see **Conferences, divisions and groups**), `rowsTotal` (the
rows before the run's maximum charge cut it) and `truncated` (`true` when the budget covered
only part of the rows). A league whose table or scoring chart did not load after retries
carries `unavailableParts`, for example `["topScorers"]`; the rest of the league is returned.
In an `onlyChanges` run every row also carries `changeType` (`new` or `changed`), and a nested
record `unchangedRows`, the rows left out (see **Only changes**).

**Top scorer row** — rank, player, player page, club, club page, club code (`teamCode`, for
example `EDM`), goals, assists where published, points where published (`points`: the NHL
chart is ranked by them, goals plus assists), position, nationality.

### Join with match data

The ids are the source's own, under the same names as in our match Actors (football,
basketball, hockey, baseball, tennis and the multi-sport one), so a table joins its matches
without matching names:

| Here | In a match record | Example (Premier League, 14 October 2026) |
|---|---|---|
| `tournamentId` | `tournamentId` — the league, the same for every season | `dYlOSQOD` |
| `tournamentSeasonId` | `tournamentSeasonId` — the season | `SY30SsKF` |
| `tournamentStageId` | `tournamentStageId` — the stage | `CfoA8Dmm` |
| `teamId` in a table row | `home.id` and `away.id` | Coventry `GOvB22xg` |
| `leagueUrl` | `tournamentUrl` | `https://www.flashscore.com/football/england/premier-league/` |

The values above were read from this Actor's local run and from a cloud run of the football
Actor on the same day. **League tables**, **Home table**, **Away table**, **Form, Over/Under and
HT/FT** and **Top scorers** show the league and season ids next to every row; **Top scorers**
also the player's and club's ids (`playerId`, `teamId`).

**Tennis rankings.** There are no league tables for tennis. A ranking row's `playerId` is the
Flashscore ID of the player, the same as `home.id` and `away.id` in our tennis match records
(checked on 14 October 2026 on 12 players of local runs, such as Alex de Minaur `EZgZ9Xfh` and
Hubert Hurkacz `zuHOBnY9`), so matches join with the rankings of their players. Our tennis
career Actor reads another source and has no Flashscore IDs: it joins with these rows only by
name.

### Regular loads: deduplicate and update

Every record carries `scrapedAt`, the moment it was output; of two copies of the same row keep
the one with the larger `scrapedAt`. To receive only the rows that changed, see **Only changes**.
The key of a row:

| Rows | Key |
|---|---|
| League tables, home, away and extra tables | `tournamentStageId` + `table` + `venue` + `window` + `line` + `group` + `teamId` |
| Top scorers | `tournamentSeasonId` + `playerId` |
| Rankings | `rankingUrl` + `rankingDate` + `playerId` |
| Elo tables | `rankingUrl` + `rankingDate` + `player` (Elo rows have no ID) |

These are the fields of the rows layout. In the nested layout the table is the list a row comes
from (`standings`, `homeTable`, `awayTable`, or an entry of `extraTables` with its `table`,
`venue`, `window` and `line`), and the league ids are on the league record. Fields that do not
apply are empty, so keep them in the key as empty values; a team listed in its conference and in
its division appears in both groups, so `group` stays in the key.

### Only changes

Turn on `onlyChanges` for scheduled runs: a run then returns only the rows that are new or
changed since the previous run with the same `changesName`, each marked with `changeType`
(`new` or `changed`), and **bills only them**: `league-result` (or `ranking-result`) only for
a league or ranking with at least one such row, `standings-row`, `top-scorer-row` and
`ranking-row` only for those rows. A league or ranking without changes gives no record at all,
and the status message counts the rows skipped. A run where nothing changed pays only the start
and the run minimum (see the costs below). The first run with a new name returns everything.
`changesKey`, the older name of `changesName`, still works in saved tasks, schedules and API
calls: the same value keeps the same memory, and when both are set `changesName` is used.

A row counts as changed when any of its values changes: rank, games, points, goals, form,
streak, zone, group. Links, logos, the zone colour and the next fixture do not count. A table
row is remembered by league season, table (main, home, away, each form window and each
over/under line), group and team; a top scorer and a ranking row by player, an Elo row by
name. Only rows the run returned are remembered: rows cut by the maximum charge of the run
come back as new in the next run. A row that leaves the table stays remembered.

**The memory lives in your account**: a named key-value store `standings-changes-<changesName>`
(`standings-changes-default` by default), one record per league season and ranking, about 3 KB
for a 20-team league with 50 top scorers and 80 KB for the whole ATP table (local runs). A run
that found no changes does not rewrite it. Named storages are kept until you delete them
([Apify docs](https://docs.apify.com/platform/storage/usage#data-retention)). To start over,
delete the store or pick a new name. Use one name per schedule, and do not start a run while another run with
the same name is still going: both would return and bill the same changes.

### Input

```json
{
  "leagueUrls": [
    "https://www.flashscore.com/football/england/premier-league/",
    "https://www.flashscore.com/football/spain/laliga/"
  ],
  "rankingUrls": [
    "https://www.flashscore.com/tennis/rankings/atp/"
  ],
  "includeStandings": true,
  "includeHomeAwayTables": false,
  "includeTopScorers": true,
  "maxTopScorers": 50,
  "includeEloRankings": true,
  "eloTours": ["atp", "wta"]
}
```

The input form lists popular leagues to pick from: the top five European leagues, the
Champions League, the Championship, the Eredivisie, Liga Portugal, MLS, the NBA, the
Euroleague, the NHL and MLB. For any other league, open the sport, pick the country and
the competition, copy the address from the browser and paste it into the same field.
**Ranking pages** lists all eight ranking tables. A league from the list whose new season
has not started gives a free explanatory record instead of a table of zeros (the NBA in
mid-October, see **Limits**). That is the whole setup. An address copied without `https://`
(`www.flashscore.com/...`) works as is.

**An empty input runs the example.** When no league page, no ranking page and no Elo
table is asked for, the Actor runs the example leagues of the input form — the Premier
League and LaLiga with top scorers — and bills them like any other run (about $0.014 at the
prices below). Turning on `includeEloRankings` alone is enough to skip the example:
then only the Elo tables are returned.

### Conferences, divisions and groups

Leagues split into conferences, divisions or groups (NBA, NHL, MLB, UEFA Nations League)
carry the group on every row. Teams appear once per group, so an NBA team has a conference
row and a division row: the NBA table is 60 rows for 30 teams, the NHL table 64 rows for 32.

**A team is billed once per table.** Its first row is a billed table row; its repeat rows in
the same table (the division after the conference) are free. The NBA table is 30 billed rows,
the NHL table 32; the home and away tables and each extra table count their teams the same way.
`rowsBilled` in the record shows the count.

Win percentage comes from the source where the table is ranked by it (NBA, MLB); games
behind is computed against the group leader. Hockey tables are ranked by points, so NHL rows
carry `winPercentage` and `gamesBehind` as `null`. Every row also has the current streak,
overtime and shoot-out wins and losses, and the "Qualified for" mark once a place is
secured. The streak is read from the form string, which carries the last five results; a
streak that fills all five is checked in the league's form table, whose windows of the last
10 matches (and 15, 20... where the league has them) count wins, draws and losses. That is
one more request per league, free, made only when some team is on such a streak and not at
all when `tableTypes` already holds `form`. All six matches of a team won gives `W6`; the
last 10 all won gives `W10+`; where the table cannot tell, the streak stays `W5+`, five or
more. `streakMin` holds the length as a number: exact for `W6`, the lower bound for `W10+`.
Measured on 14 October: Manchester City `W6` and Barcelona `W8` this season, the Edmonton
Oilers an exact `W5` (six games, the first one not won), the Denver Nuggets ending 2025/2026 on
`W10+`. Shorter streaks are exact: `L2`. Home and away tables are checked only against the
`form-home` and `form-away` tables, when you ask for them.

Archive season pages (for example `nba-2025-2026`) work. When the current stage is a
playoff bracket, the regular-season table of the same season is returned. A competition
split into several leagues or stages returns the stage its page opens on: for the UEFA
Nations League that is League A only (16 teams); Leagues B to D are not returned.

### Extra tables

`tableTypes` adds more tables from the league's standings page: `form` (the table over
the last 5, 10... matches, one table per window), `over-under` (matches above and below
each total line, one table per line) and `ht-ft` (half-time/full-time outcomes, football
only), each also as `-home` and `-away`. One extra request per table type per league. A
league that does not publish a table simply returns none.

**Every extra table is billed like the main table**, a team once per table, and one type can
hold several tables. Measured on 14 October: on the Premier League `form` gave two windows
(last 5 and last 10), `over-under` seven lines and `ht-ft` one table — 10 tables, 200 rows,
200 billed. On the NHL `form` and `over-under` gave 11 tables of 64 rows — 704 rows, 352
billed.

**Keep only the lines and windows you need.** The source sends every line and window in one
answer, so `overUnderLines` (for example `["2.5"]`) and `formWindows` (for example `["5"]`)
cost no extra request: the other tables are dropped before billing. On the NHL, `form` and
`over-under` with `overUnderLines: ["5.5"]` and `formWindows: ["5"]` kept 2 of the 11 tables:
64 billed rows instead of 352, and $0.02697 instead of $0.10473 for the run with the main
table (local runs on 14 October). A line or window the league does not have is named in the
log and the run's status message together with the ones it has; a league left with no table
at all gets a free record with the error `no table after filters`. A value that is not a
number stops the run before the first request.

### Past seasons

`lastSeasons` returns that many seasons of each league page, newest first, counting back from
the season the page opens on: `"lastSeasons": 3` on the Premier League page gives 2026/2027,
2025/2026 and 2024/2025, and on an archive page such as `nba-2009-2010` it starts from
2009/2010. `seasons` names seasons as the source writes them, anywhere in the league's
list: `2019/2020` in football, `2025` in MLB (`2019/20` and `2019-20` work too). The two fields
add up, and a season asked for twice comes once. The list of seasons is already on the league
page (the Premier League page lists 115), so a season takes the same table requests as a league
and no other page.

**Each season is billed as a league**: `league-result` plus its rows. Two leagues with
`"lastSeasons": 2` and `"seasons": ["2019/20"]` gave six league records (120 table rows, 300 top
scorers) for $0.04145 with the start in a local run on 14 October. The MLB page in October opens
on the 2026 play-offs, and `"lastSeasons": 2` gave the 2026 and 2025 regular-season tables; a
season that has not started gives the free `season not started` record, as the NBA's 2026/2027
did next to its 2025/2026 table. A season name the league does not have is named in the log and
the status message with the nearest seasons it has; when none of the named seasons exists, the
league gets a free record with the error `season not found`. The maximum charge of the run is
checked before each season. A record of a season other than the one the page opens on carries
`stageName: null`: the page names the stages of its own season only.

### Output layout

`outputLayout: nested` (the default) gives one record per league or ranking, with the
table, top scorers and ranking rows as arrays inside it. `outputLayout: rows` gives one
record per table row, top scorer and ranking position, with the league or ranking fields
repeated and a `recordType` field (`standings-row`, `top-scorer-row`, `ranking-row`,
`elo-ranking-row`, `error`). In the rows layout `standings-row` covers the main, home, away
and extra tables alike; the `table` and `venue` fields tell them apart. Billing is the same
in both layouts.

**Leagues and rankings are in two datasets of the run.** League records stay in the run's
default dataset; ranking and Elo records go to a second dataset named `rankings`. The number
of records does not change, and a table view never mixes a league with a ranking. In the
Output tab the **Rankings** output opens the second dataset; through the API its id is
`storageIds.datasets.rankings` of the run (see **League table API: call it from code**). The Export button on the
run page and `run-sync-get-dataset-items` read the default dataset only, that is the leagues.

The dataset views work in both layouts. In the nested layout **League tables**, **Top
scorers**, **Home table**, **Away table**, **Form, Over/Under and HT/FT** and **Rankings**
unwind the arrays of each record into one row per team, scorer or position. A league or a
ranking that returned no data shows there as one row with its `error`. In the rows layout
every view lists all row records of its dataset, told apart by `recordType`, `table` and
`venue`.

### Download as a spreadsheet

- **Export** on the run page: pick a view — **League tables**, **Top scorers**, **Home
  table**, **Away table** or **Form, Over/Under and HT/FT** — and **Excel**: one row per team
  or player, numbers as numbers. The default
  **All fields** puts each league on one row with every row of its tables spread over the
  columns: about 1,300 for the Premier League with 50 top scorers, more with home, away and extra
  tables, and CSV and Excel files stop at 2,000 columns. Export covers the league records
  only ("The Export button on the Run page exports only the default dataset",
  [Apify docs](https://docs.apify.com/platform/actors/development/actor-definition/dataset-schema/multiple-datasets)):
  for the rankings open the run's **Storage** tab, pick the rankings dataset in the **Dataset**
  list and export it there. The **Rankings** output in the Output tab opens the same dataset in
  its view.
- **API**: `https://api.apify.com/v2/datasets/<datasetId>/items?view=standingsRows&format=xlsx`
  gives the League tables view as an Excel file, with column headers named after the fields
  (`rank`, `team`, `points`); `view=topScorerRows`, `homeTableRows`, `awayTableRows`,
  `extraTableRows` and `overview` work the same way, `view=rankingRows` on the rankings
  dataset. Add `clean=true` to drop empty rows.
- **Prefer Excel to CSV** for opening in Excel: in locales that use `;` as the list separator a
  CSV file opens as one column.
- `outputLayout: rows` writes one record per row instead, so even **All fields** exports
  flat; the number of records grows to one per row.

### League table API: call it from code

Any Apify client works. The **API** tab of this page has ready code with this Actor's full name
and your token; below, `<owner>` stands for the owner part of that name and `$APIFY_TOKEN` for
your own token. `maxTotalChargeUsd` caps what one run may cost: the run stops
before the next league once the cap is reached (see **What a run costs**). A synchronous call
waits for the run and returns the items of the default dataset, that is the league records;
a run longer than 300 seconds answers with 408, so keep synchronous calls small and use a
client library for big runs.

```bash
curl -X POST "https://api.apify.com/v2/acts/<owner>~league-standings-top-scorers/run-sync-get-dataset-items?view=standingsRows&format=csv&maxTotalChargeUsd=0.05" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"leagueUrls": ["https://www.flashscore.com/football/england/premier-league/"]}'
```

Rankings are in the `rankings` dataset: start the run, take its id from
`storageIds.datasets.rankings` and read that dataset.

```bash
curl -X POST "https://api.apify.com/v2/acts/<owner>~league-standings-top-scorers/runs?waitForFinish=60&maxTotalChargeUsd=0.1" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rankingUrls": ["https://www.flashscore.com/tennis/rankings/atp/"]}'
## data.storageIds.datasets.rankings in the answer is <rankingsDatasetId>
curl "https://api.apify.com/v2/datasets/<rankingsDatasetId>/items?view=rankingRows&format=csv" \
  -H "Authorization: Bearer $APIFY_TOKEN"
```

Python, with [apify-client](https://docs.apify.com/api/client/python/docs):

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("<owner>/league-standings-top-scorers").call(
    run_input={"leagueUrls": ["https://www.flashscore.com/football/england/premier-league/"]},
    max_total_charge_usd=0.05,
)
print(run.status, run.status_message)
if run.status == "SUCCEEDED":
    rows = client.dataset(run.default_dataset_id).list_items(view="standingsRows", skip_empty=True).items
    for row in rows:
        print(row["rank"], row["team"], row["points"])
```

JavaScript, with [apify-client](https://docs.apify.com/api/client/js/docs), rankings included:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('<owner>/league-standings-top-scorers').call(
    {
        leagueUrls: ['https://www.flashscore.com/football/england/premier-league/'],
        rankingUrls: ['https://www.flashscore.com/tennis/rankings/atp/'],
    },
    { maxTotalChargeUsd: 0.1 },
);
console.log(run.status, run.statusMessage);
if (run.status === 'SUCCEEDED') {
    const tables = await client.dataset(run.defaultDatasetId).listItems({ view: 'standingsRows' });
    for (const row of tables.items) console.log(row.rank, row.team, row.points);
    const rankingsId = run.storageIds.datasets.rankings;
    const rankings = await client.dataset(rankingsId).listItems({ view: 'rankingRows' });
    for (const row of rankings.items) console.log(row.rank, row.player, row.points);
}
```

The status message is worth printing: it says what was output and why anything was not.

### Use it from an MCP client

Apify's MCP server at `https://mcp.apify.com` exposes Store Actors as tools to MCP clients
such as Cursor, VS Code or GitHub Copilot. The client can find this Actor with the
`search-actors` tool and run it with `call-actor`, or you can pin it as a tool of its own:
`https://mcp.apify.com?tools=<owner>/league-standings-top-scorers`. `call-actor` returns the
run's status and storage ids rather than the rows; `get-dataset-items` reads the league records
from the default dataset and the rankings from the `rankings` dataset. Details:
[Apify MCP server](https://docs.apify.com/platform/integrations/mcp).

### Schedules, webhooks and integrations

- **[Schedules](https://docs.apify.com/platform/schedules)** run the Actor or a saved task on a
  cron expression: a daily run keeps tables and rankings current, and with `onlyChanges` it
  returns and bills only the rows that changed (see **Only changes**).
- **[Webhooks](https://docs.apify.com/platform/integrations/webhooks)** call your URL when a run
  succeeds or fails.
- An integration that reads the run's default dataset gets the league records; for rankings,
  give it the dataset id from `storageIds.datasets.rankings`.

### When a league returns nothing

A league, ranking or Elo table that gives no data gets a record with `error` and `details`
instead, and is not billed. It shows as one row in the table views and in **Leagues** (a
ranking: in **Rankings**):

| `error` | What happened |
|---|---|
| `invalid url` | the value is not a web address; no request is made |
| `page not found` | the source answered 404: check the address |
| `not a league page` | the page loaded but is not a competition page |
| `no league table` | the competition has no table or scoring chart, as with knockout cups such as the FA Cup |
| `no table after filters` | `overUnderLines` and `formWindows` left no table of the league; `details` names the lines and windows it has |
| `no active season stage` | the league has no active stage at the source, most likely the off-season |
| `season not found` | none of the seasons named in `seasons` exists for the league; `details` names the nearest ones |
| `season not started` | the new season is listed, but no team has played yet |
| `source unavailable` | the page or its table feeds did not load after retries; the address may be fine |
| `ranking id not found`, `no data from ranking feed` | the page carries no ranking table, or the table came back empty |
| `no data from elo report`, `unknown tour` | the Elo report could not be read, or `eloTours` holds a value other than `atp` and `wta` |

Every run ends with a status message: what was output, how many inputs were not and why
(for example `1 page not found, 1 without a league table`), parts the source did not return,
lines and windows that were not found, where the rankings are, and a stop on the spending
limit. A run where nothing loaded because the source did not respond **fails** with that
explanation, so webhooks and alerts see it; when the first three inputs all fail that way, it
stops trying the rest. A wrong league or ranking address never fails a run: it succeeds with
the breakdown in its status.

### Good for

- **Betting and trading models** — form tables split by venue, plus the scoring chart.
- **Season dashboards** — schedule a daily run and keep tables current.
- **Fantasy and content** — top scorers with position and nationality, ready to publish.

### Limits

- **Off-season pages show the season the source shows.** In October the NBA page
  already carries the 2026/2027 table with zero games played. When no team of a table
  has played yet, the Actor returns an explanatory record (`error: "season not
  started"`) instead of a table of zeros, and nothing is billed for it. Pass the archive
  page (`nba-2025-2026`) for last season's final table. A league with no table at all
  gets an explanatory record too, also not billed.
- **Top scorers exist where the source publishes a scoring chart.** Football and hockey
  do (the NHL chart carries goals, assists and points); the NBA and MLB pages have none, and
  neither do some cup competitions.
- **Assists are only present where the source lists them**, which varies by league.
- **A long streak can stay open.** The form string carries the last five results; the form
  table settles a longer streak only when every match of a window has the same result, so
  some show as `W5+` (five or more) or `W10+`.
- **One stage per competition.** The UEFA Nations League returns League A only.
- **Repeated inputs are dropped.** The same league or ranking page given twice (with or
  without `www`, a trailing slash, a `#/...` tab anchor or query parameters), or two
  addresses of the same table, is fetched and billed once; the log names what was
  skipped.
- **Temporary load errors are retried twice** with a short pause; if the page still does
  not load, the record says `source unavailable` and is not billed.

### Rankings

Eight tennis tables are available, each on its own page under
`https://www.flashscore.com/tennis/rankings/`: `atp`, `wta`, `atp-race`, `wta-race`,
`atp-doubles`, `wta-doubles`, `atp-doubles-race` and `wta-doubles-race`; **Ranking pages** in
the input form lists all eight. Each row carries rank, previous rank, the move between them,
points, the number of tournaments counted where the source lists it, nationality and a link to
the player, and `tied` when players share a position. In the doubles tables the row is a pair:
`player`, `playerId`, `country` and `playerUrl` hold both partners separated by `|`. The result
also carries `rankingDate` — the Monday the ranking was published on, so this week's table is
distinguishable from last week's. `maxRankingRows` goes up to 5,000, which covers the whole
ATP singles table (2,308 rows on 14 October). Age, points change and best rank are not
published in the ranking feed. Ranking records are in the run's `rankings` dataset (see **Output layout**).

### Elo ratings

Turn on `includeEloRankings` and the run also returns the tennis Elo
leaderboards published by **Tennis Abstract** (tennisabstract.com) — one record
per tour (`eloTours`, ATP and WTA by default; an empty list also means both), marked
`"type": "elo-ranking"`, in the `rankings` dataset next to the ATP and WTA rankings.
Elo is computed from match results rather than tournament points, so it can
disagree with the official ranking and gives models a separate signal.

Each row carries the Elo rank, player and profile link, age, overall Elo,
separate hard-, clay- and grass-court Elo with their own ranks, career peak Elo
with the month it was reached, the official ATP/WTA rank next to the Elo rank,
and the source's log-difference measure of how far the two disagree.
`maxRankingRows` caps these tables too. The record carries `rankingDate` — the
source updates the report roughly once a week.

The Elo data comes from the public reports of Tennis Abstract (tennisabstract.com); each
`elo-ranking` record names it in `dataSource`.

### What a run costs

Billing is per row, not per run, so the price of a league follows the size of its
table and its scoring chart:

| Event | Price |
|---|---:|
| League processed (each season, when several are asked) | $0.001 |
| One team row of a table (repeat rows of a team in the same table are free) | $0.00027 |
| One scoring-chart row | $0.00001 |
| Ranking or Elo table processed | $0.001 |
| One ranking or Elo row | $0.00078 |
| Run minimum top-up, a unit (see below) | $0.00001 |

The platform also charges $0.00005 when a run starts.

A 20-team league table is $0.001 + 20 × $0.00027 = **$0.0064**; with the season scoring
chart at the default cap of 50 rows it comes to **$0.0069**. The NBA table of 60 rows has 30
teams and costs $0.001 + 30 × $0.00027 = **$0.0091**; the NHL table, 32 teams, **$0.00964**,
or **$0.01014** with its 50 top scorers. Home and away tables add two tables of 20 teams
($0.0108) to a 20-team league, and the Premier League `over-under` set of seven lines adds
140 billed rows ($0.0378); `overUnderLines` and `formWindows` keep only the tables you pay for.

A ranking page at the default cap of 100 rows is $0.001 + 100 × $0.00078 = **$0.079**;
Elo tables are billed the same way. The ATP singles table has about 2,300 rows (2,308 on 14
October) at $0.00078 a row: all of it, with `maxRankingRows` raised to its maximum of 5,000,
came to $1.80129 in a local run. Lower `maxTopScorers` and `maxRankingRows` if you only need
the top of each table — that is the whole of the bill. The form as first opened (two leagues
with 50 top scorers each, the ATP top 10) came to $0.00005 + 2 × ($0.001 + 20 × $0.00027 +
50 × $0.00001) + ($0.001 + 10 × $0.00078) = **$0.02265** with the start in a local test run at
these prices on 14 October.

Each season of `lastSeasons` and `seasons` is billed as a league. With `onlyChanges` the league
or ranking event and the rows are billed only for what changed (see **Only changes**). Team
logos and the streak check are free.

A league with no table, or with a table where nobody has played yet, returns an
explanatory record and is not billed as a table.

**Run minimum.** A run that reads the source and returns little or nothing billable — a
league before its season starts, pages that do not exist — still uses compute, storage and
traffic on the platform. Such a run is topped up with the `run-minimum` event ($0.00001 a
unit) to 1.5 times its platform usage at Apify's list prices (compute after the first 5
seconds, which Apify covers). A run that fails, is aborted gracefully or reaches its timeout is topped up only to 1.25
times the usage, which covers what the run cost and no more; an abort without the graceful option (the default in Console and the
API) stops the run at once, and it pays only what was charged along the way. Everything the
run was charged, the start included, counts toward the minimum, so a run whose tables cover
its usage pays no final top-up, and a run with nothing to bill usually comes to a few
hundredths of a cent. In a run longer than a minute, part of the top-up can be charged along
the way, right after inputs that returned nothing billable, so that a crash later on cannot
leave it unpaid. That part is capped at 1.25 times the usage so far and counts toward the
final top-up, but it is **not refunded** if later inputs return tables that would have
covered the run. If you pick residential proxies in the Proxy field, their traffic counts at
Apify's $8 per GB. The amount and the reason are written to the run's status message and the
log. Users on the Apify free plan never pay it, and the minimum never goes past your maximum
cost per run: before each input the run checks that the minimum for more work still fits and
otherwise stops and says so, and a page that does not answer is not retried once the retry
would no longer fit.

**The maximum charge you set for a run is respected row by row.** When the budget runs
out, the last table is returned only with the rows that were billed (in the nested layout
it is marked `truncated: true`, `rowsTotal` keeps the full count), and the run stops: the
next league or ranking is neither fetched nor returned. A team the budget does not cover
leaves the table with all its rows. A table is never billed without rows: when the budget
left does not cover a table together with at least one of its rows, that table is neither
returned nor billed. The run's status message says how many tables were output and how many
inputs were not processed.

### Speed

Plain HTTP requests, no browser: three per league with the default options, five with home
and away tables and one more per extra table type, plus one free request for the form table
when a team is on a streak of five or more; a ranking is two (three past 100 rows), an Elo
table one. The first requests of a run go out directly, which is faster; the proxy from the
input takes over after about 140 requests or as soon as the source refuses a direct request,
and is set up only then, so a run that stays direct does not wait for it. If a proxy group or
country picked in the input is not available to your plan, the run goes on directly and its
status says so. The run starts with 512 MB of memory. When the platform restarts a run (moves
it to another server), the league seasons, rankings and Elo tables output before the restart
are skipped and not billed again; only a table in progress at that moment can be.

Measured in the cloud on 14 October 2026:

- the Premier League table over a direct connection: the whole run took 4.1 seconds at
  256 MB of memory and 6.5 seconds at 1 GB;
- the same league with its scoring chart through the Apify proxy: 14 seconds of reading;
- three leagues with home, away, form, over/under and HT/FT tables, a ranking and an Elo
  table: 38 seconds, at a memory peak of 175 MB.

### FAQ

**Is there a football league table API with top scorers?**
Yes. Paste a league page into `leagueUrls`: one call returns the full table with rank, points,
goals, recent form and qualification zone, plus the season scoring chart with club, position and
nationality. The same works for basketball, hockey and handball leagues. Call it from any language
over HTTP and get JSON, CSV or Excel; see **League table API: call it from code**.

**Why did a league come back without a table?**
Its record says why in `error` and `details` (see **When a league returns nothing**):
most often an address with a typo, a knockout cup, or a league between seasons. Such a
record is free, and the run's status message sums them up.

**Where are the rankings?**
In the run's second dataset, `rankings`: the **Rankings** output in the Output tab,
`storageIds.datasets.rankings` in the API. The Export button and `run-sync-get-dataset-items`
return the league records only.

**Why does the NBA table have 60 rows when the league has 30 teams?**
Each team is listed in its conference and in its division. Both rows are returned, and only
the first one is billed.

**How do I get one row per team in Excel or CSV?**
Press **Export**, pick the view **League tables** and **Excel**; see **Download as a
spreadsheet**. `outputLayout: rows` writes
one record per row for all tables, scorers and rankings.

**How do I join a table with matches from your match Actors?**
By `tournamentSeasonId` and `teamId` (`home.id`, `away.id` in a match), or by `tournamentId`
for the league across seasons; see **Join with match data**.

**How do I get last season's table?**
Set `lastSeasons` to 2 (this season and the last one) or name it in `seasons`, such as
`2025/2026`; each season is billed as a league. See **Past seasons**.

**How do I get only what changed since the last run?**
Turn on `onlyChanges`; unchanged rows are neither returned nor billed. See **Only changes**.

**What does `truncated` mean?**
The run reached the maximum cost you set while billing that table: it holds the rows that
were paid for, and `rowsTotal` says how many there were.

**What does a league cost?**
About $0.0064 for a 20-team table and $0.0069 with 50 top scorers, plus $0.00005 for the run
start; see **What a run costs**.

**Why does a run that returned little show a `run-minimum` charge?**
Reading the source costs compute, storage and traffic even when nothing billable comes back.
Such a run is topped up to 1.5 times what it used on the platform, or 1.25 times when it fails,
is aborted gracefully or reaches its timeout; see **Run minimum** above.

**Do I need residential proxies?**
No. The Actor reads public pages and feeds with plain HTTP requests, directly and then
through the default Apify datacenter proxy.

### Related Actors

- Matches of the same leagues, joined on `tournamentId`, `tournamentSeasonId`, `tournamentStageId` and
  `teamId`: [Tennis Scraper — ATP, WTA | Live Scores, Odds, Point by Point](https://apify.com/scapypuppy/tennis-scraper-atp-wta); [Football & Soccer Scraper — xG, Live Scores, Odds](https://apify.com/scapypuppy/football-soccer-scraper); [Basketball Scraper — NBA, EuroLeague, Box Scores](https://apify.com/scapypuppy/basketball-scraper-nba); [Ice Hockey Scraper — NHL, KHL Box Scores & Odds](https://apify.com/scapypuppy/ice-hockey-scraper-nhl); [Baseball Scraper — MLB Box Scores, NPB, KBO, Odds](https://apify.com/scapypuppy/baseball-scraper-mlb); [Multi-Sport Scraper — Table Tennis, Handball +21](https://apify.com/scapypuppy/multi-sport-scraper).
- [Tennis Player Career Scraper — Serve Stats, Surface Splits](https://apify.com/scapypuppy/tennis-player-career-scraper) — a tennis player's career match by match, with serve and return
  statistics.

### Support

Open an issue in the **Issues** tab of this Actor's page. Paste the link to the run (from the
run's page in Console): an Actor issue with a run link shares that run, its input, log and
storages with the developer automatically, and nothing else in your account
([Apify docs](https://docs.apify.com/account/collaboration/general-resource-access)). Say what
you expected and what you got instead. Do not post your API token or any other secret.

### Disclaimer

This is an **unofficial** Actor. It is not affiliated with, endorsed by, or
sponsored by Flashscore, Tennis Abstract or any league, club or competition
named here. All trademarks belong to their respective owners. The Actor reads
publicly available pages and returns the data they already show to any visitor.

# Changelog

This Actor's version history is a separate document: https://apify.com/scapypuppy/league-standings-top-scorers/changelog.md

# Actor input Schema

## `leagueUrls` (type: `array`):

Pick popular leagues from the list or paste any Flashscore league page of any sport, like https://www.flashscore.com/football/italy/serie-a/.

## `rankingUrls` (type: `array`):

Tennis rankings from the list: ATP and WTA singles, doubles and the Race of each, eight in all. A ranking page pasted from flashscore.com works too. Rankings appear under Output, Rankings.

## `includeStandings` (type: `boolean`):

Rank, played, wins, draws, losses, goals, points, form, qualification zone and next fixture per team. Off-season tables give a note, not billed.

## `includeHomeAwayTables` (type: `boolean`):

Two more tables built only from home matches and only from away matches. One request each; rows are billed like the main table.

## `tableTypes` (type: `array`):

Form, Over/Under and HT/FT tables, one request each per league, rows billed like the main table. One type can hold several tables (see README).

## `overUnderLines` (type: `array`):

Keep only these total lines of the Over/Under tables, like 2.5. Each line is a separate billed table (the Premier League has seven); empty keeps all. No extra request.

## `formWindows` (type: `array`):

Keep only these Form tables, by the number of latest matches: 5, 10. Each window is a separate billed table; empty keeps all. No extra request.

## `includeTopScorers` (type: `boolean`):

Season scoring chart: player, club, goals, position and nationality, with links to both profiles.

## `maxTopScorers` (type: `integer`):

How many rows of the scoring chart to keep. Billed per row, so this is also a cost cap.

## `lastSeasons` (type: `integer`):

How many seasons to return, newest first, counting back from the season the league page opens on: 3 gives it and the two before it. Each season is billed as a league.

## `seasons` (type: `array`):

Seasons by name, as the source writes them: 2024/2025 in football, 2025 in MLB (2024-25 works too). Adds to Last seasons; a season the league does not have is named in the status with the nearest ones.

## `includeEloRankings` (type: `boolean`):

Tennis Elo leaderboards: overall Elo plus hard, clay and grass ratings, age, peak Elo and the official rank next to the Elo rank. One request per tour.

## `eloTours` (type: `array`):

Which tours to fetch Elo ratings for, when Elo ratings are on. Empty means both.

## `maxRankingRows` (type: `integer`):

How many players to keep from each ranking table, Elo tables included. Billed per row, so this is also a cost cap: the ATP singles table has about 2,300 rows.

## `onlyChanges` (type: `boolean`):

Return and bill only the rows that are new or changed since the last run with the same changes name; a league or ranking without changes gives no record. The first run returns everything.

## `changesName` (type: `string`):

Name of the memory of the option above, the store standings-changes-<name>, kept until you delete it; empty means default. Use one name per schedule, and never run two runs with one name at once.

## `changesKey` (type: `string`):

Older name of Changes name, kept hidden so saved tasks keep their memory; Changes name wins when both are set.

## `outputLayout` (type: `string`):

Nested: one record per league or ranking, rows inside it; the League tables, Top scorers and Rankings views still show one row each. Rows: one record per table row with a recordType field, ready for CSV. Billing is the same.

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

The first requests go out directly, which is faster; this proxy takes over after about 140 requests or when the source refuses a direct one. Datacenter proxies are enough.

## Actor input object example

```json
{
  "leagueUrls": [
    "https://www.flashscore.com/football/england/premier-league/",
    "https://www.flashscore.com/football/spain/laliga/"
  ],
  "rankingUrls": [
    "https://www.flashscore.com/tennis/rankings/atp/"
  ],
  "includeStandings": true,
  "includeHomeAwayTables": false,
  "tableTypes": [],
  "overUnderLines": [],
  "formWindows": [],
  "includeTopScorers": true,
  "maxTopScorers": 50,
  "seasons": [],
  "includeEloRankings": false,
  "eloTours": [
    "atp",
    "wta"
  ],
  "maxRankingRows": 10,
  "onlyChanges": false,
  "outputLayout": "nested",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `standings` (type: `string`):

One row per team of each league table: rank, team and logo, played, wins, draws, losses, goals, points, form and streak, zone and next fixture, with the league, season and team ids that join the rows with the match Actors.

## `topScorers` (type: `string`):

One row per player of each league scoring chart: club, goals, assists, points where published, position and nationality.

## `rankings` (type: `string`):

One row per position of each ATP/WTA ranking and Tennis Abstract Elo table, from the run's rankings dataset. To download it as a file, open the run's Storage tab and pick the rankings dataset: the Export button of the run exports only the default dataset.

## `overview` (type: `string`):

One row per league record with row counts, the truncated flag and the error and details of a league that holds no data.

# 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 = {
    "leagueUrls": [
        "https://www.flashscore.com/football/england/premier-league/",
        "https://www.flashscore.com/football/spain/laliga/"
    ],
    "rankingUrls": [
        "https://www.flashscore.com/tennis/rankings/atp/"
    ],
    "maxRankingRows": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("scapypuppy/league-standings-top-scorers").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 = {
    "leagueUrls": [
        "https://www.flashscore.com/football/england/premier-league/",
        "https://www.flashscore.com/football/spain/laliga/",
    ],
    "rankingUrls": ["https://www.flashscore.com/tennis/rankings/atp/"],
    "maxRankingRows": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("scapypuppy/league-standings-top-scorers").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 '{
  "leagueUrls": [
    "https://www.flashscore.com/football/england/premier-league/",
    "https://www.flashscore.com/football/spain/laliga/"
  ],
  "rankingUrls": [
    "https://www.flashscore.com/tennis/rankings/atp/"
  ],
  "maxRankingRows": 10
}' |
apify call scapypuppy/league-standings-top-scorers --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scapypuppy/league-standings-top-scorers"
        }
    }
}
```

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/JQ87f6oloKVbdhHWR/builds/V8RynxFHnD3oaxMeN/openapi.json
