List a team squad
The squad endpoint returns a team's personnel for a season. It is one call and needs no navigation, but the response mixes three things you probably want to separate: current players, players who have left, and the coach.
1. The call
Section titled “1. The call”curl "https://api.statscore.com/v2/participants/136761/squad?token=YOUR_TOKEN&competition_id=1498&season_id=65485"| Parameter | Where | Required | Why you want it |
|---|---|---|---|
id | path | yes | the team |
season_id | query | no | scopes the squad to one season |
competition_id | query | no | disambiguates when a team plays several competitions |
Without season_id you get the team's personnel as the provider currently holds it, which is
not the same as "the squad in season X". Pass both when you are rendering a historical page.
2. Three kinds of row in one array
Section titled “2. Three kinds of row in one array”Everything arrives in api.data.participants[], and every entry has type: "person". What
distinguishes them are two fields that are easy to miss.
{ "api": { "data": { "participants": [ { "id": 271441, "name": "Mikael Ishak", "short_name": "Mikael Ishak", "acronym": "ISH", "type": "person", "area_name": "Sweden", "shirt_nr": "9", "team_connection": "current", "details": { "position_name": "Attacker", "position_id": "34", "birthdate": "1993-03-31", "born_place": "Stockholm", "height": 185, "weight": 81, "is_retired": "no", "subtype": "athlete", "absence": null } } ] } }}3. Field-level traps
Section titled “3. Field-level traps”Four things in details that break naive rendering:
| Field | Trap |
|---|---|
shirt_nr | can be null, and is a string when present ("9", "41"). Do not sort numerically without coercing, and do not assume it exists. |
shirt_nr | not unique across the array. Two entries shared "17" and two shared "7" in our sample - because one of each pair is transfer_outer. Unique only within current. |
height / weight | either a number (185) or an empty string ("") for players without data. Number("") is 0, so an unguarded coercion produces a 0 cm player. |
born_place | often "". Fine to show conditionally, never as a required field. |
Positions come as a pair, and the ids are stable:
position_id | position_name |
|---|---|
31 | Goalkeeper |
32 | Defender |
33 | Midfielder |
34 | Attacker |
Group by position_id rather than by the localized position_name - the name changes with the
lang parameter, the id does not.
details.absence was null on every row we captured. It presumably carries injuries or
suspensions, but we have not seen it populated, so do not build a UI that depends on its shape.
4. Putting it together
Section titled “4. Putting it together”import { apiGet } from "./client.js"; // z recipe 01
const POSITION_ORDER = { 31: 0, 32: 1, 33: 2, 34: 3 }; // GK, DEF, MID, ATT
/** * Zwraca sklad podzielony na obecnych zawodnikow, odeszlych i sztab. * Jedna tablica z API zawiera wszystkie trzy grupy naraz. */export async function getSquad(teamId, { seasonId, competitionId } = {}) { const data = await apiGet(`/v2/participants/${teamId}/squad`, { season_id: seasonId, competition_id: competitionId, });
// "" i null znacza "brak danych" - Number("") to 0, wiec pilnujemy tego jawnie const num = (v) => (v === "" || v == null ? null : Number(v));
const people = (data.participants ?? []).map((p) => { const d = p.details ?? {}; return { id: p.id, name: p.name, shortName: p.short_name, country: p.area_name, // shirt_nr bywa null i jest stringiem, gdy jest shirtNr: num(p.shirt_nr), // "current" albo "transfer_outer" - dopasowujemy do "current", nie wykluczamy reszty isCurrent: p.team_connection === "current", // trener siedzi w tej samej tablicy, rozni sie tylko subtype isCoach: d.subtype === "coach", position: d.position_id ? { id: Number(d.position_id), name: d.position_name } : null, birthdate: d.birthdate || null, heightCm: num(d.height), weightKg: num(d.weight), absence: d.absence ?? null, }; });
const byPosition = (a, b) => (POSITION_ORDER[a.position?.id] ?? 99) - (POSITION_ORDER[b.position?.id] ?? 99) || (a.shirtNr ?? 999) - (b.shirtNr ?? 999);
return { players: people.filter((p) => p.isCurrent && !p.isCoach).sort(byPosition), departed: people.filter((p) => !p.isCurrent && !p.isCoach).sort(byPosition), staff: people.filter((p) => p.isCoach), };}# Python - ten sam podzialPOSITION_ORDER = {31: 0, 32: 1, 33: 2, 34: 3} # GK, DEF, MID, ATT
def get_squad(team_id: int, season_id: int | None = None, competition_id: int | None = None): data = api_get(f"/v2/participants/{team_id}/squad", season_id=season_id, competition_id=competition_id)
def num(v): # "" i None znacza brak danych; int("") rzuca, a Number("") w JS dawalo 0 return None if v in ("", None) else float(v)
people = [] for p in data.get("participants", []): d = p.get("details") or {} people.append({ "id": p["id"], "name": p.get("name"), "country": p.get("area_name"), "shirt_nr": num(p.get("shirt_nr")), # dopasowujemy do "current", nie wykluczamy znanych innych wartosci "is_current": p.get("team_connection") == "current", # trener jest w tej samej tablicy "is_coach": d.get("subtype") == "coach", "position": {"id": int(d["position_id"]), "name": d.get("position_name")} if d.get("position_id") else None, "birthdate": d.get("birthdate") or None, "height_cm": num(d.get("height")), "weight_kg": num(d.get("weight")), "absence": d.get("absence"), })
def key(p): pos = POSITION_ORDER.get((p["position"] or {}).get("id"), 99) return (pos, p["shirt_nr"] if p["shirt_nr"] is not None else 999)
return { "players": sorted([p for p in people if p["is_current"] and not p["is_coach"]], key=key), "departed": sorted([p for p in people if not p["is_current"] and not p["is_coach"]], key=key), "staff": [p for p in people if p["is_coach"]], }Per-match appearances are a different endpoint
Section titled “Per-match appearances are a different endpoint”The squad tells you who belongs to the team, not who played. For minutes, lineups and per-match appearances you need:
| Question | Endpoint |
|---|---|
| who was in the lineup for this match, and their position | GET /v2/events/{id} -> lineups[] (lineups and formations) |
| minutes and bench for this match | GET /v2/events/{id}/participants |
| goals and cards across the season | rankings, not the squad (recipe 08) |
Errors
Section titled “Errors”| Status | Cause |
|---|---|
400 | malformed season_id or competition_id |
401 | missing, expired or malformed token |
403 | the competition is outside your contract |
404 | unknown participant id |
- Lineups and formations -
has_formations, and whereplayer_positionactually lives - Get one match with score and stats - per-match score and stats
- Show top scorers, cards and assists - season aggregates per player
- Compare two teams head to head - the rivalry view
Full reference: GET /v2/participants/{id}/squad ·
GET /v2/events/{id}/participants