Show top scorers, cards and assists
Player rankings come from the same endpoint as the league table. Nothing new to learn about navigation: it is the three-step flow from recipe 02, with a different type selected. What is new is the row shape, and it inverts one thing you learned there.
1. Find out which rankings the sport has
Section titled “1. Find out which rankings the sport has”GET /v2/standings-types?sport_id=5 lists every ranking type available for a sport, with its
columns. Soccer has 19. These are the ones you will actually want:
type_id | type_name | What it ranks | Columns |
|---|---|---|---|
2 | League standings | teams | mp, won, draw, lost, goals_scored, goals_allowed, goals_diff, pts, form |
6 | Top scorers | players | goals_scored, mp, goals_per_match, manual_rank |
7 | Cards | players | yellow_cards, red_cards, cards, pts, mp, cards_per_match, yellow_cards_per_match, red_cards_per_match |
176 | Assists | players | assists, mp, assists_per_match, manual_rank |
18 | Form table | teams | form |
47 | Streaks | teams | wins, draws, losses, no_wins, no_draws, no_losses |
39 | Most popular results | results | result, amount, pct |
244 | League standings live | teams | league columns plus is_playing, home_result, away_result, event_id |
The rest are variants of the league table (Home standings, Away standings,
Halftime standings, Second half standings, Wide standings), two aggregate types
(Overall stats with ~80 columns, Team overall stats with ~120) and two rankings
(FIFA ranking, FIFA club ranking).
2. Find the ranking for your season
Section titled “2. Find the ranking for your season”Same call as the league table, and the same three steps: catalogue, then pick, then rows.
curl "https://api.statscore.com/v2/standings?token=YOUR_TOKEN&object_type=season&object_id=65485&limit=50"Each catalogue entry carries both identifiers, so match on whichever you prefer:
{ "api": { "data": { "standings_list": [ { "id": 196367, "name": "Top scorers - Poland, PKO Ekstraklasa 2025/26", "type_id": 6, "type_name": "Top scorers", "subtype": "standings", "object_id": 65485, "object_type": "season", "object_name": "PKO Ekstraklasa 2025/26", "item_status": "active" } ] } }}subtype is a coarser grouping than type_name and takes four values in practice:
standings, standings_live, form, overall_stats.
3. Read the rows
Section titled “3. Read the rows”curl "https://api.statscore.com/v2/standings/196367?token=YOUR_TOKEN"Here is where player rankings stop looking like a league table:
{ "api": { "data": { "standings": { "id": 196367, "type_id": 6, "type_name": "Top scorers", "object_type": "season", "object_id": 65485, "groups": [ { "id": "", "name": "", "zones": [], "corrections": [], "participants": [ { "id": 1220697, "name": "Tomas Bobcek", "participant_type": "person", "participant_acronym": "BOB", "area_id": 164, "area_name": "Slovakia", "rank": 1, "last_rank": 1, "subparticipant_id": 136762, "subparticipant_name": "Lechia Gdansk", "columns": [ { "id": 6, "code": "goals_scored", "short_name": "GS", "value": 20 }, { "id": 1, "code": "mp", "short_name": "MP", "value": 30 }, { "id": 393, "code": "goals_per_match", "short_name": "GPM", "value": "0.67" } ] } ] } ] } } }}Two more things the sample shows:
- The top-level
columnsarray is empty. Stats live incolumnson each participant, with avalue. Read them from the rows, or from/v2/standings-typesif you need the definitions before the data arrives. valuetypes are mixed.goals_scoredis a number (20),goals_per_matchis a string ("0.67"). Coerce on read; do not sort on the raw value.
rank and last_rank let you show movement. zones and corrections exist on the group but were
empty arrays for both rankings we captured, so do not build a UI that requires them.
Real output
Section titled “Real output”Top scorers, Ekstraklasa 2025/26, first five of 257 rows:
| # | Player | Club | GS | MP | GPM |
|---|---|---|---|---|---|
| 1 | Tomas Bobcek | Lechia Gdansk | 20 | 30 | 0.67 |
| 2 | Karol Czubak | Motor Lublin | 18 | 32 | 0.56 |
| 3 | Jonatan Braut Brunes | Rakow Czestochowa | 16 | 32 | 0.50 |
| 4 | Mikael Ishak | Lech Poznan | 16 | 31 | 0.52 |
| 5 | Afimico Pululu | Jagiellonia | 15 | 33 | 0.45 |
Cards, same season, 384 rows:
| # | Player | Club | YC | RC | cards | Pts | MP |
|---|---|---|---|---|---|---|---|
| 1 | Juljan Shehu | Widzew Lodz | 11 | 1 | 12 | 14 | 32 |
| 2 | Ivan Zhelizko | Lechia Gdansk | 10 | 1 | 11 | 13 | 30 |
| 3 | Oskar Wojcik | Cracovia | 10 | 1 | 11 | 13 | 31 |
4. Putting it together
Section titled “4. Putting it together”import { apiGet } from "./client.js"; // z recipe 01
/** * Zwraca ranking zawodnikow dla sezonu. `typeName` to np. "Top scorers", * "Cards", "Assists" - pelna lista z /v2/standings-types. */export async function getPlayerRanking(seasonId, typeName, { scope = "season" } = {}) { const catalogue = await apiGet("/v2/standings", { object_type: "season", object_id: seasonId, limit: 50, });
// Pytanie o sezon zwraca TEZ rankingi etapow - filtrujemy po object_type sami. const entry = (catalogue.standings_list ?? []).find( (s) => s.type_name === typeName && s.object_type === scope && s.item_status === "active" ); if (!entry) return null;
const data = await apiGet(`/v2/standings/${entry.id}`); const standing = data.standings;
// Rankingi zawodnikow maja jedna grupe bez nazwy; tabele ligowe moga miec kilka. const rows = (standing.groups ?? []).flatMap((g) => g.participants ?? []);
const num = (v) => (v === "" || v == null ? null : Number(v));
return { id: standing.id, typeName: standing.type_name, scope: standing.object_type, rows: rows.map((p) => ({ rank: p.rank, lastRank: p.last_rank ?? null, // W rankingu zawodnikow participant to OSOBA, a subparticipant to KLUB. // W tabeli ligowej jest odwrotnie - participant to zespol. player: p.participant_type === "person" ? { id: p.id, name: p.name, country: p.area_name } : null, team: p.subparticipant_id ? { id: p.subparticipant_id, name: p.subparticipant_name } : { id: p.id, name: p.name }, // wartosci sa mieszane: liczby i stringi. Klucz to `code`, nie pozycja. stats: Object.fromEntries((p.columns ?? []).map((c) => [c.code, num(c.value)])), })), };}# Python - ten sam przeplywdef get_player_ranking(season_id: int, type_name: str, scope: str = "season"): catalogue = api_get("/v2/standings", object_type="season", object_id=season_id, limit=50)
# pytanie o sezon zwraca tez rankingi etapow - filtrujemy po object_type entry = next( (s for s in catalogue.get("standings_list", []) if s.get("type_name") == type_name and s.get("object_type") == scope and s.get("item_status") == "active"), None, ) if not entry: return None
standing = api_get(f"/v2/standings/{entry['id']}")["standings"]
def num(v): return None if v in ("", None) else float(v)
rows = [] for g in standing.get("groups", []): for p in g.get("participants", []): is_person = p.get("participant_type") == "person" rows.append({ "rank": p.get("rank"), "last_rank": p.get("last_rank"), # participant = osoba, subparticipant = klub (odwrotnie niz w tabeli ligowej) "player": {"id": p["id"], "name": p.get("name"), "country": p.get("area_name")} if is_person else None, "team": {"id": p.get("subparticipant_id"), "name": p.get("subparticipant_name")} if p.get("subparticipant_id") else {"id": p["id"], "name": p.get("name")}, "stats": {c["code"]: num(c.get("value")) for c in p.get("columns", [])}, })
return {"id": standing.get("id"), "type_name": standing.get("type_name"), "scope": standing.get("object_type"), "rows": rows}Availability depends on the competition
Section titled “Availability depends on the competition”Player rankings are generated, not universal. Two fields on the competition tell you what to
expect, both visible on /v2/competitions:
| Field | Ekstraklasa | Meaning |
|---|---|---|
stats_lvl | "gold" | depth of statistics: bronze / silver / gold |
generate_season_stats | true | whether season aggregates are produced at all |
A bronze competition may have a league table and no top scorers. Branch on the catalogue being
empty rather than assuming a ranking exists.
Errors
Section titled “Errors”| Status | Cause |
|---|---|
400 | limit outside the endpoint's enum, or a missing object_type / object_id pair |
401 | missing, expired or malformed token |
403 | the competition is outside your contract |
404 | unknown standings id |
- Display a league table - the same flow for teams, with groups and zones
- Compare two teams head to head - the other aggregate view
- Sync incrementally - rankings change slowly, so mirror them
Full reference: GET /v2/standings ·
GET /v2/standings-types ·
GET /v2/competitions