LeagueSpy API v2

The most accurate football lineup predictions on the market. We track injuries, suspensions, and squad rotation patterns to forecast who starts, who sits, and who's out — before anyone else.

Base URL https://api.leaguespy.com
Version 2.0.0
Protocol REST / JSON
Compare API Tiers

Quick start

1
Get your API key — contact us or request access through your account manager.
2
Make your first call:
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.leaguespy.com/v2/fixtures/updates?limit=1"
3
You'll receive lineup data in your first response. Filter by league_id or team_id to narrow results, or use updated_since to poll only for changes.

Authentication

Every request requires an API key. Pass it as a header or query parameter.

# Header (recommended)
curl -H "X-API-Key: YOUR_KEY" \
  "https://api.leaguespy.com/v2/fixtures/updates"

# Query parameter
curl "https://api.leaguespy.com/v2/fixtures/updates?api_key=YOUR_KEY"
Key security. API keys must be at least 32 characters. Never expose them in client-side code or public repositories.

Tiers

Each API key is bound to a tier. Your tier determines data freshness, update frequency, and field visibility.

TierSyncDescription
professionalEvery 3 hoursPredicted XI and bench, missing players with reasons, official lineups as soon as published. See the competitions.
realtime≤15 minFull depth: probabilities and expected minutes, squad roles, absence impact, returning players, positional battles, season statistics. See the competitions.
Your current tier is returned in every response under meta.tier and subscription.tier.

List fixture updates

GET /v2/fixtures/updates

Returns fixture lineup previews sorted by most recently updated. Includes predicted XI, bench, missing players, and confirmed lineups when available.

Query parameters

ParameterTypeDefaultDescription
limitinteger20Results per page. Range: 1–100.
offsetinteger0Pagination offset. Range: 0–5000.
team_idstring—Filter by team public ID (matches home or away).
league_idstring—Filter by league public ID.
fixture_idstring—Filter by fixture public ID.
updated_sincestring—ISO 8601 datetime with timezone. Only return fixtures updated after this time. Maximum lookback: 48 hours.
2026-06-05T08:30:00Z
cursorstring—Opaque pagination cursor for stable incremental polling. Use the next_cursor value from the previous response. Only valid together with updated_since. Cannot be combined with offset.
includestring—Optional response blocks, comma-separated. league_stats adds season statistics to player objects, broadcasts the TV and streaming broadcasters of the fixture.
langstring—Response language for names, positions and reasons, e.g. ko. See Localization.
Public IDs. All entity IDs in v2 are opaque strings, not integers. Use the IDs exactly as returned by the API.

Example request

curl -H "X-API-Key: YOUR_KEY" \
  "https://api.leaguespy.com/v2/fixtures/updates?league_id=lg_xyz789&limit=5"
Compare API Tiers

Response

{
  "data": [
    {
      "fixture_id": "VJfWANbE",
      "home_team_id": "11uGdbIt",
      "home_team_name": "Levante",
      "home_team_name_en": "Levante",
      "home_team_logo": "https://img.leaguespy.com/teams/11uGdbIt.png",
      "away_team_id": "aGpkiedc",
      "away_team_name": "Athletic Club",
      "away_team_name_en": "Athletic Club",
      "away_team_logo": "https://img.leaguespy.com/teams/aGpkiedc.png",
      "league_id": "QBIQ7xJS",
      "league_name": "La Liga",
      "league_name_en": "La Liga",
      "league_logo": "https://img.leaguespy.com/leagues/QBIQ7xJS.png",
      "country_name": "Spain",
      "country_name_en": "Spain",
      "season_id": "n82fEZtc",
      "season_name": "2026/2027",
      "starting_at": "2026-10-21T18:00:00Z",
      "status": "scheduled",
      "home_score": null,
      "away_score": null,
      "updated_at": "2026-10-21T09:16:54.882613Z",
      "lineup": {
        "home": {
          "formation": "4-1-4-1",
          "is_confirmed": false,
          "is_nomination_announced": false,
          "starting_xi": [
            {
              "name": "Ryan M.",
              "name_en": "Ryan M.",
              "player_id": "BFKC5XyX",
              "squad_role": "regular",
              "detailed_position": "Goalkeeper",
              "formation_position": 1,
              "start_xi_probability": 99,
              "bench_probability": 0,
              "expected_minutes": 90,
              "expected_sub_off_probability": 0,
              "expected_sub_off_minute": null
            },
            {
              "name": "Ratkov P.",
              "name_en": "Ratkov P.",
              "player_id": "jzHpyPj5",
              "squad_role": "backup",
              "detailed_position": "Centre Forward",
              "formation_position": 11,
              "start_xi_probability": 10,
              "bench_probability": 40,
              "expected_minutes": 60.0,
              "expected_sub_off_probability": 100,
              "expected_sub_off_minute": 60
            }
          ],
          "bench": [
            {
              "name": "Axel Tape",
              "name_en": "Axel Tape",
              "player_id": "W10cOGaz",
              "squad_role": "youth",
              "detailed_position": "Right Back",
              "start_xi_probability": 0,
              "bench_probability": 50,
              "expected_minutes": 0,
              "expected_sub_in_probability": 0,
              "expected_sub_in_minute": null
            }
          ],
          "missing": [
            {
              "name": "Primo A.",
              "name_en": "Primo A.",
              "player_id": "Y78HfNjW",
              "squad_role": null,
              "detailed_position": "Goalkeeper",
              "missing_type": "injury",
              "reason": "Shoulder Injury",
              "missed_in_row": 10,
              "days_missed": 157,
              "expected_return": {
                "from": "2026-11-29",
                "to": "2027-02-24"
              },
              "absence_impact": null
            },
            {
              "name": "Sotelo H.",
              "name_en": "Sotelo H.",
              "player_id": "oSwdFDDa",
              "squad_role": "backup",
              "detailed_position": "Central Midfield",
              "missing_type": "doubtful",
              "reason": "Knee Injury",
              "missed_in_row": 3,
              "days_missed": 31,
              "expected_return": null,
              "absence_impact": "low",
              "play_probability": 30,
              "start_xi_probability": 17
            }
          ],
          "new_arrivals": [],
          "returning": [
            {
              "name": "Marc Santos",
              "name_en": "Marc Santos",
              "player_id": "F1PnN5eB",
              "squad_role": "youth",
              "detailed_position": "Left Back",
              "games_missed": 2,
              "days_out": 8,
              "returning_to": "bench"
            }
          ],
          "positional_battles": [
            {
              "position": "Right Back",
              "formation_position": 2,
              "contenders": [
                {
                  "name": "Toljan J.",
                  "name_en": "Toljan J.",
                  "player_id": "0G8ozWJE",
                  "squad_role": "rotation",
                  "probability": 50
                },
                {
                  "name": "Nacho Pérez",
                  "name_en": "Nacho Pérez",
                  "player_id": "fIaRlrWZ",
                  "squad_role": "regular",
                  "probability": 50
                }
              ]
            }
          ],
          "lineup_changes": {
            "count": 4,
            "in": [
              {
                "name": "Toljan J.",
                "name_en": "Toljan J.",
                "player_id": "0G8ozWJE"
              },
              {
                "name": "Mandi A.",
                "name_en": "Mandi A.",
                "player_id": "6DY8rtL6"
              },
              {
                "name": "Olasagasti J. A.",
                "name_en": "Olasagasti J. A.",
                "player_id": "6dWOnhit"
              },
              {
                "name": "Ratkov P.",
                "name_en": "Ratkov P.",
                "player_id": "jzHpyPj5"
              }
            ],
            "out": [
              {
                "name": "Nacho Pérez",
                "name_en": "Nacho Pérez",
                "player_id": "fIaRlrWZ"
              },
              {
                "name": "Ifeanyi Ndukwe",
                "name_en": "Ifeanyi Ndukwe",
                "player_id": "c3gj48hd"
              },
              {
                "name": "Enzo Bardeli",
                "name_en": "Enzo Bardeli",
                "player_id": "RrdKtGfe"
              },
              {
                "name": "Thiago Fernández",
                "name_en": "Thiago Fernández",
                "player_id": "k2fmKhNO"
              }
            ]
          }
        }
      }
    }
  ],
  "meta": {
    "total": 1,
    "limit": 20,
    "offset": 0,
    "next_cursor": null,
    "has_more": false,
    "tier": "realtime",
    "timezone": "UTC"
  },
  "rate_limit": {
    "remaining": 119,
    "limit": 120,
    "resets_in_seconds": 60
  },
  "subscription": {
    "plan": "LeagueSpy Realtime",
    "sport": "Football",
    "tier": "realtime"
  }
}
{
  "data": [
    {
      "fixture_id": "VJfWANbE",
      "home_team_id": "11uGdbIt",
      "home_team_name": "Levante",
      "home_team_name_en": "Levante",
      "home_team_logo": "https://img.leaguespy.com/teams/11uGdbIt.png",
      "away_team_id": "aGpkiedc",
      "away_team_name": "Athletic Club",
      "away_team_name_en": "Athletic Club",
      "away_team_logo": "https://img.leaguespy.com/teams/aGpkiedc.png",
      "league_id": "QBIQ7xJS",
      "league_name": "La Liga",
      "league_name_en": "La Liga",
      "league_logo": "https://img.leaguespy.com/leagues/QBIQ7xJS.png",
      "country_name": "Spain",
      "country_name_en": "Spain",
      "season_id": "n82fEZtc",
      "season_name": "2026/2027",
      "starting_at": "2026-10-21T18:00:00Z",
      "status": "scheduled",
      "home_score": null,
      "away_score": null,
      "updated_at": "2026-10-21T09:16:54.882613Z",
      "lineup": {
        "home": {
          "formation": "4-1-4-1",
          "is_confirmed": false,
          "is_nomination_announced": false,
          "starting_xi": [
            {
              "name": "Ryan M.",
              "name_en": "Ryan M.",
              "player_id": "BFKC5XyX",
              "formation_position": 1
            },
            {
              "name": "Ratkov P.",
              "name_en": "Ratkov P.",
              "player_id": "jzHpyPj5",
              "formation_position": 11
            }
          ],
          "bench": [
            {
              "name": "Axel Tape",
              "name_en": "Axel Tape",
              "player_id": "W10cOGaz"
            }
          ],
          "missing": [
            {
              "name": "Primo A.",
              "name_en": "Primo A.",
              "player_id": "Y78HfNjW",
              "missing_type": "injury",
              "reason": "Shoulder Injury"
            },
            {
              "name": "Sotelo H.",
              "name_en": "Sotelo H.",
              "player_id": "oSwdFDDa",
              "missing_type": "doubtful",
              "reason": "Knee Injury"
            }
          ],
          "new_arrivals": [],
          "lineup_changes": {
            "count": 4,
            "in": [
              {
                "name": "Toljan J.",
                "name_en": "Toljan J.",
                "player_id": "0G8ozWJE"
              },
              {
                "name": "Mandi A.",
                "name_en": "Mandi A.",
                "player_id": "6DY8rtL6"
              },
              {
                "name": "Olasagasti J. A.",
                "name_en": "Olasagasti J. A.",
                "player_id": "6dWOnhit"
              },
              {
                "name": "Ratkov P.",
                "name_en": "Ratkov P.",
                "player_id": "jzHpyPj5"
              }
            ],
            "out": [
              {
                "name": "Nacho Pérez",
                "name_en": "Nacho Pérez",
                "player_id": "fIaRlrWZ"
              },
              {
                "name": "Ifeanyi Ndukwe",
                "name_en": "Ifeanyi Ndukwe",
                "player_id": "c3gj48hd"
              },
              {
                "name": "Enzo Bardeli",
                "name_en": "Enzo Bardeli",
                "player_id": "RrdKtGfe"
              },
              {
                "name": "Thiago Fernández",
                "name_en": "Thiago Fernández",
                "player_id": "k2fmKhNO"
              }
            ]
          }
        }
      }
    }
  ],
  "meta": {
    "total": 1,
    "limit": 20,
    "offset": 0,
    "next_cursor": null,
    "has_more": false,
    "tier": "professional",
    "timezone": "UTC"
  },
  "rate_limit": {
    "remaining": 29,
    "limit": 30,
    "resets_in_seconds": 60
  },
  "subscription": {
    "plan": "LeagueSpy Professional",
    "sport": "Football",
    "tier": "professional"
  }
}

Get single fixture

GET /v2/fixtures/{fixture_id}

Returns lineup data for a single fixture by its public ID. Response is the same shape as one item from the data array above, with rate_limit and subscription at the top level.

Path parameter

ParameterTypeDescription
fixture_idstringThe fixture's public ID.

Example

curl -H "X-API-Key: YOUR_KEY" \
  "https://api.leaguespy.com/v2/fixtures/fx_a1b2c3"

Response

Returns the fixture object wrapped in a data object, with rate_limit and subscription at the top level.

{
  "data": {
    "fixture_id": "fx_a1b2c3",
    "home_team_name": "FC Barcelona",
    "away_team_name": "Real Madrid",
    "lineup": { /* ... */ }             // Same structure as list response
  },
  "rate_limit": { /* ... */ },
  "subscription": { /* ... */ }
}


Health check

GET /health

No authentication required. Returns a simple status object.

{ "status": "ok" }

Fixture object

One item of data in /v2/fixtures/updates, and data of /v2/fixtures/{fixture_id}.

FieldTypeDescription
fixture_idstringFixture ID (opaque string).
home_team_idstringHome team ID.
home_team_namestringHome team name (localized with ?lang=).
home_team_name_enstringEnglish / original name, on every request (use it for matching).
home_team_logostring (URL)Home team logo. A generic placeholder image when the team has no logo.
away_team_idstringAway team ID.
away_team_namestringAway team name (localized with ?lang=).
away_team_name_enstringEnglish / original name, on every request (use it for matching).
away_team_logostring (URL)Away team logo (placeholder image when none).
league_idstringCompetition ID.
league_namestringCompetition name (localized with ?lang=).
league_name_enstringEnglish / original name, on every request (use it for matching).
league_logostring (URL)Competition logo (placeholder image when none).
country_namestringCountry of the competition. International competitions use a region: World, Europe, Asia, South America, North & Central America…
country_name_enstringEnglish / original name, on every request (use it for matching).
season_idstringSeason ID.
season_namestringSeason label: 2026/2027 for seasons across the turn of the year, 2026 for calendar-year seasons.
starting_atstringKickoff time, UTC (YYYY-MM-DDTHH:MM:SSZ). Follows any rescheduling.
statusstringMatch status: scheduled, live, finished, postponed, cancelled… See status values.
home_scoreinteger | nullHome team goals: the current score while the match is live, the final score once it is finished. null before kickoff, for postponed or cancelled matches and when no score is known. A goal does not change updated_at, and a match leaves /v2/fixtures/updates one hour after kickoff, so read the live and the final score from /v2/fixtures/{fixture_id}.
away_scoreinteger | nullAway team goals: the current score while the match is live, the final score once it is finished. null before kickoff, for postponed or cancelled matches and when no score is known. A goal does not change updated_at, and a match leaves /v2/fixtures/updates one hour after kickoff, so read the live and the final score from /v2/fixtures/{fixture_id}.
periodstring | nullPart of a live match: first_half, half_time, second_half, break (before extra time, between its halves or before penalties), extra_time, penalties. null when the match is not live.
minuteinteger | nullMatch minute while the ball is in play (1 to 45, 46 to 90, 91 to 120). In added time it stays at 45, 90, 105 or 120 and extra_minute counts the added minutes (45+2 = minute 45, extra_minute 2). null at half-time, in breaks, during penalties and when the match is not live. Accurate to about 30 seconds.
extra_minuteinteger | nullAdded-time minutes on top of minute; 0 outside added time, null when minute is null.
updated_atstringLast time this fixture changed: lineup data, status or kickoff time. ISO 8601 UTC, the number of fractional-second digits varies. Use it for incremental polling.
lineupobjectlineup.home and lineup.away team objects.
broadcastsarray | nullTV and streaming broadcasters by country, see broadcasts. Only with ?include=broadcasts; null otherwise. Optional, present only when it applies.

Lineup object

Each side, lineup.home and lineup.away.

FieldTypeDescription
formationstring | nullFormation, e.g. 4-2-3-1. Predicted while is_confirmed is false, official afterwards. null when not known.
is_confirmedbooleantrue once the official starting lineup is published. From then on starting_xi, bench and formation hold the official data.
is_nomination_announcedbooleantrue once the official matchday squad is known. Currently switches together with is_confirmed.
starting_xiarrayStarting eleven, predicted before confirmation and official after. [] when no lineup is available yet.
bencharraySubstitutes, predicted before confirmation and official after. Can be [].
missingarrayPlayers who are out or doubtful for this match. [] when none are known.
new_arrivalsarrayRecent signings named in this matchday squad. [] when none.
returningarrayRT Players in this match's XI or bench who missed the team's most recent match(es). At most 4.
positional_battlesarrayRT Positions where the starting choice is open.
lineup_changesobject | nullChanges in starting_xi compared with the team's previous match. See lineup changes. null when the two lineups cannot be compared.

Data lifecycle

  • A fixture appears in /v2/fixtures/updates once lineup data exists for it, from up to 7 days before kickoff, and stays until 1 hour after kickoff (48 hours for postponed, interrupted, cancelled and similar fixtures).
  • Predicted lineups and missing players arrive first, up to 7 days before kickoff (a team that plays twice in a week has both matches), where a prediction is available for the competition. The prediction is updated with every new piece of information until the official lineup. Before confirmation is_confirmed is false and starting_xi / bench hold the prediction.
  • The official lineup replaces the prediction as soon as it is published, usually 40 to 60 minutes before kickoff (about 50 to 60 minutes in the top European leagues and UEFA club competitions). is_confirmed switches to true and is never switched back by a later prediction. For some lower-division matches the official lineup arrives only shortly before kickoff.
  • Every change of lineup data, status or starting_at updates updated_at, so polling with updated_since sees all of them.
  • /v2/fixtures/{fixture_id} returns matches from the start of your subscription onwards.

Player objects

Players in starting_xi and bench.

starting_xi[]

FieldTypeDescription
namestringPlayer name (localized with ?lang=).
name_enstringEnglish / original name, on every request (use it for matching).
player_idstring | nullPlayer ID. null only if the player could not be identified.
squad_rolestring | nullRT Role in the squad this season. See squad_role.
detailed_positionstringRT Usual position. See positions. Always English. Optional, present only when it applies.
detailed_position_labelstringRT Position in the requested language. Only with ?lang=. Optional, present only when it applies.
formation_positioninteger | nullSlot in the formation grid, 1 to 11. See formation_position.
exact_positionstringOfficial tactical slot code, e.g. RCB. Only on some official lineups. Optional, present only when it applies.
start_xi_probabilityinteger | nullRT Chance (%) to start, 0 to 99: starts in the team's last 10 competitive matches. Players in an official lineup have 99. null without recent match history.
bench_probabilityinteger | nullRT Chance (%) to be named among the substitutes, 0 to 99.
expected_minutesnumberRT Most likely minutes played: 90, or the expected substitution minute when a substitution is more likely than not. 0 to 90.
expected_sub_off_probabilityinteger | nullRT Chance (%) to be substituted off. Goalkeepers 0.
expected_sub_off_minuteinteger | nullRT Expected substitution minute, 1 to 89. null when the chance is below 25 %.
was_doubtfulbooleantrue when the player was doubtful before the match but is in the lineup. Optional, present only when it applies.
debutbooleantrue when this can be the player's first appearance for the club (see new_arrivals). Optional, present only when it applies.
returning_after_missedintegerOn some official lineups: consecutive matches the player missed before this one. Optional, present only when it applies.
league_statsobjectSeason statistics, see league_stats. Only with ?include=league_stats.

bench[]

FieldTypeDescription
namestringPlayer name.
name_enstringEnglish / original name, on every request (use it for matching).
player_idstring | nullPlayer ID.
squad_rolestring | nullRT Role in the squad this season.
detailed_positionstringRT Usual position (always English). Optional, present only when it applies.
detailed_position_labelstringRT Position in the requested language. Only with ?lang=. Optional, present only when it applies.
start_xi_probabilityinteger | nullRT Chance (%) to start. In a predicted lineup never above the lowest starting_xi player at the same detailed_position. Official substitutes have 0.
bench_probabilityinteger | nullRT Chance (%) to be named among the substitutes.
expected_minutesnumber | nullRT Most likely minutes: 0, or the expected minutes after coming on when that is more likely than not.
expected_sub_in_probabilityinteger | nullRT Chance (%) to come on. Goalkeepers 0.
expected_sub_in_minuteinteger | nullRT Expected minute of coming on, 1 to 89. null below 25 %.
was_doubtfulbooleanDoubtful before the match but named in the squad. Optional, present only when it applies.
debutbooleanPossible first appearance for the club. Optional, present only when it applies.
league_statsobjectSeason statistics. Only with ?include=league_stats.

Missing player object

Players in missing[]: out or doubtful for this match.

FieldTypeDescription
namestringPlayer name.
name_enstringEnglish / original name, on every request (use it for matching).
player_idstring | nullPlayer ID.
squad_rolestring | nullRT Role in the squad this season.
detailed_positionstringRT Usual position (always English). Optional, present only when it applies.
detailed_position_labelstringRT Position in the requested language. Only with ?lang=. Optional, present only when it applies.
missing_typestringCategory of the absence. doubtful means the player may still play. See missing_type.
reasonstring | nullShort reason from a fixed list, e.g. Knee Injury, Red Card, Transfer to <club> (<country>). See reasons. Always English; the translation is in reason_label. null when no reason is known.
reason_labelstringReason in the requested language. Only with ?lang=. Optional, present only when it applies.
missed_in_rowintegerRT Consecutive played team matches (friendlies included), right before this one, without the player in the matchday squad (counted since he joined the team). 0 = he was in the last squad. 10 with no earlier squad on record = 10 or more.
days_missedinteger | nullRT Days between the first match of the current absence and this match. null when no match has been missed yet.
expected_returnobject | nullRT Estimated return window of an injured or suspended player: the first and last date he is expected back. For a suspension both dates are the date of the first match after the ban. Updated as new information arrives. null for other absences and when no estimate is available.
expected_return.from / .tostringRT Dates, YYYY-MM-DD. from equals to when a single date is known.
absence_impactstring | nullRT How strongly the team should feel the absence. See absence_impact.
start_xi_probabilityintegerRT Only for some doubtful players: chance (%) to start, 0 to 100. Optional, present only when it applies.
play_probabilityintegerRT Only for some doubtful players: chance (%) to play any minutes, 0 to 100. Optional, present only when it applies.
transfer_datestringOnly for transfer_out: date the player left (YYYY-MM-DD). Optional, present only when it applies.
to_team_countrystringOnly for transfer_out: country of the new club (localized with ?lang=). Optional, present only when it applies.
to_team_country_enstringEnglish / original name, on every request (use it for matching). Optional, present only when it applies.
to_team_tierinteger | nullOnly for transfer_out: league level of the new club (1 = top division). Optional, present only when it applies.
league_statsobjectSeason statistics. Only with ?include=league_stats.

New arrival object

Players in new_arrivals[]: joined the club recently and are named in this match's XI or bench. Listed for the first 3 matches after the move.

FieldTypeDescription
namestringPlayer name.
name_enstringEnglish / original name, on every request (use it for matching).
player_idstringPlayer ID.
signed_fromstring | nullPrevious club with its country, Club (Country) (localized with ?lang=). null when not known.
signed_from_enstringEnglish / original name, on every request (use it for matching). Optional, present only when it applies.
transfer_datestringDate the player joined (YYYY-MM-DD).
stagestringDebut progress at the new club. See stage.
games_since_arrivalintegerTeam matches played since the player joined, 0 to 2.
startsintegerStarts for the club since joining, 0 to 2.

Returning player object

RT Players in returning[]: named in this match's XI or bench after missing the team's most recent match(es). At most 4 per team, longest absences first.

FieldTypeDescription
namestringRT Player name.
name_enstringRT English / original name, on every request (use it for matching).
player_idstringRT Player ID.
squad_rolestring | nullRT Role in the squad this season.
detailed_positionstring | nullRT Usual position (always English).
detailed_position_labelstringRT Position in the requested language. Only with ?lang=. Optional, present only when it applies.
games_missedintegerRT Consecutive team matches missed before this one, 1 to 10 (10 = 10 or more).
days_outinteger | nullRT Days between the first missed match and this match.
returning_tostringRT starting_xi or bench: where the player is listed for this match.

Positional battle object

RT Items of positional_battles[]: positions where the player in the predicted XI is not a clear choice — a challenger is a bench player at the same position whose starts in recent matches are close to, and not above, the starter's. probability is each player's chance to start this match, so the starter's counts his place in the predicted XI and the challengers share at most the rest (e.g. 78 vs 22).

FieldTypeDescription
positionstringRT Contested position (always English).
position_labelstringRT Position in the requested language. Only with ?lang=. Optional, present only when it applies.
formation_positionintegerRT Slot in the formation grid, 1 to 11.
contendersarrayRT Candidates, 2 or more. The first one is the player currently in starting_xi.
contenders[].namestringRT Player name.
contenders[].name_enstringRT English / original name, on every request (use it for matching).
contenders[].player_idstringRT Player ID.
contenders[].squad_rolestring | nullRT Role in the squad this season.
contenders[].probabilityintegerRT Chance (%) that this player starts, 0 to 99.

Lineup changes object

lineup_changes compares starting_xi with the starting eleven of the team's previous finished match in any competition. Club friendlies are skipped; for national teams friendlies count. Before confirmation it uses the predicted XI, after confirmation the official one. null when there is no previous match with a complete lineup, when the current XI is not complete yet, and after the team plays another match, until the comparison is refreshed (at the latest on the day of the match).

FieldTypeDescription
countintegerPlayers in starting_xi who did not start the previous match. 0 = the same eleven.
inarrayPlayers who start now but did not start the previous match.
outarrayPlayers who started the previous match but are not in starting_xi now.
in[].name / out[].namestringPlayer name (localized with ?lang=).
in[].name_en / out[].name_enstringEnglish / original name, on every request (use it for matching).
in[].player_id / out[].player_idstringPlayer ID.

Season statistics (league_stats)

Optional block on starting_xi[], bench[] and missing[] players, added with ?include=league_stats. Always refers to the player's domestic league, also for cup and international matches.

FieldTypeDescription
league_stats.current_seasonobject | nullThe player's record for this team in the current league season. null when there is none. Only with ?include=league_stats.
league_stats.previous_seasonobject | nullPrevious league season (club with most appearances). Filled only until the team has played 4 league matches of the new season. Only with ?include=league_stats.
…league / league_idstringLeague name (localized with ?lang=) and ID. Only with ?include=league_stats.
…league_enstringEnglish / original name, on every request (use it for matching). Only with ?include=league_stats.
…seasonstringSeason label. Only with ?include=league_stats.
…team / team_idstringTeam the numbers belong to (localized with ?lang=) and its ID. Only with ?include=league_stats.
…team_enstringEnglish / original name, on every request (use it for matching). Only with ?include=league_stats.
…appearances / starts / sub_appearancesintegerMatches played, started, and entered as a substitute. Only with ?include=league_stats.
…substituted_off / bench_appearancesintegerMatches substituted off; matches named on the bench. Only with ?include=league_stats.
…minutes / goals / assistsintegerMinutes, goals, assists. Only with ?include=league_stats.
…yellow_cards / second_yellow_cards / red_cardsintegerYellow cards, second yellows, direct red cards. Only with ?include=league_stats.

Localization

Add ?lang=ko to either fixtures endpoint, or ask us to set Korean as your key's default language. Without a language parameter (or with ?lang=en) the response is in English, unless your key has a default language. GET /v2/languages lists the available languages.

Names are translated in place: team, league, country and player names (also inside league_stats), signed_from and to_team_country. A name without a translation is returned in English. The English original is always available next to it in *_en (home_team_name_en, name_en, …), on every request, so you can match against your own data.

Fixed values are never translated: detailed_position, positional_battles[].position and reason always stay in English. With a language, their translation is added in detailed_position_label, position_label and reason_label. Codes such as status, missing_type, squad_role, absence_impact and stage, IDs, numbers, dates, formations and season labels are never translated.

We announce any change to the language or format of response values in advance, by email and in the changelog.


Broadcasts (broadcasts)

Optional block on the fixture, added with ?include=broadcasts: the broadcasters known for the match, one entry per country. England, Scotland, Wales and Northern Ireland are listed together as United Kingdom. Broadcast information usually appears about three days before kickoff; an empty array means no broadcaster is known yet. Broadcasters can be added or change without a change of updated_at, so read the block when you show the match.

FieldTypeDescription
countrystringCountry (localized with ?lang=). Optional, present only when it applies.
country_enstringEnglish name of the country, on every request. Optional, present only when it applies.
channels[].channelstringBroadcaster or channel. Korean broadcasters in Korean with ?lang=ko where they write their name in Korean. Optional, present only when it applies.
channels[].channel_enstringOriginal name of the broadcaster, on every request. Optional, present only when it applies.
channels[].typestringtv (a TV channel) or streaming (a streaming service or app). Optional, present only when it applies.

Meta, rate limit & subscription

meta is part of every list response; rate_limit and subscription of every response.

FieldTypeDescription
meta.totalinteger | nullTotal matching fixtures. null in cursor mode.
meta.limitintegerPage size used.
meta.offsetinteger | nullOffset used. null in cursor mode.
meta.next_cursorstring | nullCursor for the next page. null on the last page.
meta.has_morebooleantrue when more pages are available.
meta.tierstringprofessional or realtime.
meta.timezonestringAlways UTC.
rate_limit.remainingintegerRequests left in the current one-minute window.
rate_limit.limitintegerRequests allowed per minute.
rate_limit.resets_in_secondsintegerSeconds until the window resets.
subscription.planstringPlan name, e.g. LeagueSpy Realtime.
subscription.sportstringAlways Football.
subscription.tierstringprofessional or realtime.

Value lists

status

Fixtures in postponed, suspended, interrupted, cancelled, abandoned or awarded stay in /v2/fixtures/updates for 48 hours after kickoff. A status or kickoff change updates updated_at.

ValueMeaning
scheduledNot started yet.
tbaKickoff time not fixed yet.
delayedKickoff pushed back on the day.
liveIn play (including half-time, extra time and penalties).
interruptedStopped during play (e.g. weather) and may resume.
finishedFinished (also after extra time or penalties).
postponedPostponed. starting_at shows the new date once it is known.
suspendedStopped, to be continued on another day.
cancelledCancelled, will not be played.
abandonedStopped and will not be continued.
awardedResult decided off the pitch (e.g. walkover).
unknownStatus not confirmed yet.

missing_type

ValueMeaning
injuryInjured or ill.
suspendedSuspended (cards, red card or disciplinary ban).
doubtfulDoubtful: may still play. start_xi_probability and play_probability may be given.
national_teamAway on national team duty.
out_of_selectionFit but not in the matchday squad (coach's decision, rested, not registered).
transfer_outLeft the club recently (transfer or loan). Shown for the first few matches after the move.
otherUnavailable for another reason (e.g. lacking match fitness, personal reasons).

absence_impact

ValueMeaning
criticalKey player, absence just started. Major effect expected.
highClear effect expected.
mediumNoticeable effect.
lowSmall effect.
minimalLittle or no effect (fringe or youth player).
nullNot known (player's role not classified).

squad_role

ValueMeaning
key_playerOne of the team's most important players this season.
regularFirst-choice starter. A long-injured, ill, banned or doubtful player keeps the role he had when fit.
rotationPlays regularly but rotates with others.
backupSquad player with limited playing time.
youthYoung player with limited first-team time.
nullNot classified (e.g. too few matches this season).

new_arrivals[].stage

ValueMeaning
pre_debutFirst time in a matchday squad for the new club.
bench_debutNamed in a squad before, has not played yet.
playedHas played for the club, not started yet.
startedHas started for the club.

returning[].returning_to

ValueMeaning
starting_xiReturns in the starting XI.
benchReturns on the bench.

Absence reasons (reason)

reason always comes from this list or one of the transfer templates. With ?lang=ko it is returned in Korean (right column).

ValueKorean (?lang=ko)
Abdominal strain복근 부상
Achilles Tendon Injury아킬레스건 부상
Ankle Injury발목 부상
Arm Injury팔 부상
Back Injury허리 부상
Broken Hand손 골절
Broken Leg다리 골절
Broken ankle발목 골절
Broken calfbone종아리뼈 골절
Broken cheekbone광대뼈 골절
Broken collarbone쇄골 골절
Broken hipbone고관절 골절
Broken jawbone턱뼈 골절
Broken nose코뼈 골절
Broken rib갈비뼈 골절
Broken shinbone정강이뼈 골절
Broken tailbone꼬리뼈 골절
Broken thighbone대퇴골 골절
Calf Injury종아리 부상
Chest Injury가슴 부상
Coach's decision감독 판단
Concussion뇌진탕
Contusion타박상
Convalescence회복 중
Cruciate Ligament Injury십자인대 부상
Dislocation탈구
Doping도핑 징계
Doubtful출전 불투명
Elbow Injury팔꿈치 부상
Excluded명단 제외
Eye injury눈 부상
Face Injury얼굴 부상
Finger Injury손가락 부상
Foot Injury발 부상
Forearm injury아래팔 부상
Fracture골절
Groin Injury사타구니 부상
Hamstring Injury햄스트링 부상
Hand Injury손 부상
Head Injury머리 부상
Health problems건강 문제
Heart Problems심장 이상
Heel Injury발뒤꿈치 부상
Hernia탈장
Hip Injury고관절 부상
Illness질병
Inactive명단 제외
Injury부상
International duty국가대표 차출
Jaw injury턱 부상
Knee Injury무릎 부상
Knee Ligament Injury무릎 인대 부상
Knock가벼운 부상
Lack of regular playing time at club소속팀 출전 시간 부족
Lacking Match Fitness경기 감각 부족
Leg Injury다리 부상
Ligament Injury인대 부상
Loan agreement임대 계약 조항
Lower Back Injury허리 부상
Lower-Body Injury하체 부상
Meniscus Injury반월판 부상
Muscle Injury근육 부상
Muscle Tear근육 파열
Muscle Tightness근육 경직
Neck Injury목 부상
Not considered전력 외
Not in squad명단 제외
Off the roster엔트리 제외
Other기타
Overload피로 누적
Pelvis Injury골반 부상
Personal Reasons개인 사정
Red Card퇴장 징계
Rest휴식
Ribs Injury갈비뼈 부상
Shin Injury정강이 부상
Shoulder Injury어깨 부상
Stomach Disorder위장 장애
Surgery수술
Suspended출장 정지
Tendon Injury힘줄 부상
Thigh Injury허벅지 부상
Toe Injury발가락 부상
Tooth injury치아 부상
Transfer이적
Transfer negotiations이적 협상
Upper Back Injury등 부상
Upper-Body Injury상체 부상
Without a club무소속
Wrist Injury손목 부상
Yellow Cards경고 누적

Transfer templates

Transfer to <club> (<country>), Transfer/loan to <club> (<country>), Loan ended (returned to <club> (<country>))

Positions

Values of detailed_position and positional_battles[].position. Defender, Midfielder and Attacker appear when only the line is known.

ValueKorean (?lang=ko)
Goalkeeper골키퍼
Centre Back센터백
Left Back레프트백
Right Back라이트백
Left Wing Back왼쪽 윙백
Right Wing Back오른쪽 윙백
Defensive Midfield수비형 미드필더
Central Midfield중앙 미드필더
Attacking Midfield공격형 미드필더
Left Midfield왼쪽 미드필더
Right Midfield오른쪽 미드필더
Left Wing왼쪽 윙어
Right Wing오른쪽 윙어
Striker스트라이커
Centre Forward센터포워드
Second Striker세컨드 스트라이커
Defender수비수
Midfielder미드필더
Attacker공격수

formation_position

1 is the goalkeeper, then each line from defence to attack, each line read from right to left from the team's own view. Example 4-3-3: 1 GK, 2–5 back four (2 right back … 5 left back), 6–8 midfield, 9–11 forwards.

formation

Read from defence to attack, goalkeeper not counted, e.g. 4-2-3-1, 4-3-3, 4-4-2, 3-4-2-1, 4-1-4-1, 3-4-3, 3-5-2, 5-4-1, 5-3-2, 3-4-1-2, 3-1-4-2, 4-3-1-2, 4-1-3-2, 4-4-1-1, 4-2-2-2, 4-5-1.

exact_position codes

GK, RB, RCB, CB, LCB, LB, RWB, LWB, RDM, DM, LDM, RCM, CM, LCM, RM, LM, RAM, AM, LAM, RW, LW, CF, ST.

Probabilities and minutes

All probabilities are whole percentages, 0 to 100 (start_xi_probability and bench_probability are capped at 99). In an official lineup starters have start_xi_probability: 99 and substitutes 0. Minute fields are 1 to 89; null means not expected (chance below 25 %).

Null, empty and absent

An empty array [] means nothing is known, not an error. null means not known. Flags (was_doubtful, debut) appear only when true. New keys may be added; clients should ignore unknown keys.


Error codes

All errors return a JSON body with error_code and detail. Rate limit errors also include retry_after.

{
  "error_code": "RATE_LIMIT_EXCEEDED",
  "detail": "Rate limit exceeded. Please wait before making more requests.",
  "retry_after": 60
}
HTTPError CodeMeaningRetry?
400INVALID_DATETIMEBad datetime formatNo — fix request
400INVALID_ID_FORMATMalformed public IDNo — fix ID
400UPDATED_SINCE_TOO_OLDLookback exceeds 48 hoursNo — use offset pagination for full dataset
400INVALID_CURSORCorrupted or tampered cursorNo — restart query without cursor
400CURSOR_EXPIREDCursor older than 2 hoursNo — restart query without cursor
400INVALID_PARAMScursor + offset used together, or cursor without updated_sinceNo — fix request
401MISSING_API_KEYNo API key providedNo — add header
401INVALID_API_KEYKey not recognizedNo — check key
403VERSION_ACCESS_DENIEDNo v2 access on this keyNo — contact us
404ID_NOT_FOUNDEntity doesn't existNo — verify ID
405—Wrong HTTP methodNo — use GET
429RATE_LIMIT_EXCEEDEDToo many requestsYes — wait retry_after
500INTERNAL_ERRORServer errorYes — retry 30s
503SERVICE_UNAVAILABLETemporarily unavailableYes — retry 30s

Response headers

Included on every response.

HeaderDescription
X-Request-IdUnique request identifier. Include in bug reports.
X-RateLimit-LimitMax requests per minute for your tier.
X-RateLimit-RemainingRequests remaining in current window.
X-RateLimit-ResetUnix timestamp when the window resets.
Retry-AfterSeconds to wait. Present on 429 responses only.

Initial data sync

New integrations should follow this two-phase approach to build and maintain their local dataset:

# Phase 1: Full dataset download (one-time)
# Use offset pagination without updated_since
all_fixtures = []
offset = 0
sync_started_at = datetime.now(timezone.utc).isoformat()

while True:
    resp = httpx.get(API_URL, headers=headers, timeout=30,
                     params={"limit": 100, "offset": offset})
    page = resp.json()
    all_fixtures.extend(page["data"])

    if not page["meta"]["has_more"]:
        break
    offset += page["meta"]["limit"]

# Phase 2: Incremental updates (ongoing)
# Switch to updated_since + cursor
last_check = sync_started_at

while True:
    params = {"limit": 100, "updated_since": last_check}
    cursor = None

    while True:
        if cursor:
            params["cursor"] = cursor
        resp = httpx.get(API_URL, headers=headers, params=params, timeout=30)
        data = resp.json()

        for fixture in data["data"]:
            upsert_fixture(fixture)  # Your upsert logic

        if not data["meta"]["has_more"]:
            break
        cursor = data["meta"]["next_cursor"]

    last_check = datetime.now(timezone.utc).isoformat()
    time.sleep(300)  # Poll every 5 minutes
Phase 1 uses offset. The initial full download uses standard offset pagination because updated_since has a 48-hour lookback limit. Once you have the full dataset, switch to cursor-based incremental polling for all subsequent updates.

Cursor pagination

When polling for updates using updated_since, use cursor-based pagination instead of offset. Cursor pagination guarantees data consistency — you won't miss records or see duplicates, even when data changes between page requests.

How it works

Each response includes a next_cursor field in the meta object. Pass this value as the cursor query parameter in your next request to continue from exactly where you left off.

Why not offset? With offset pagination, new updates can shift the dataset between requests — causing skipped or duplicated records. Cursor pagination uses a stable bookmark that is unaffected by new data arriving between pages.

Example: multi-page fetch

# 1. First request — start with updated_since, no cursor
GET /v2/fixtures/updates?updated_since=2026-06-05T08:00:00Z&limit=50

# Response:
{
  "data": [...50 fixtures...],
  "meta": {
    "next_cursor": "eyJ1Ijoi...abc.d4e5f6",
    "has_more": true,
    "total": null
  }
}

# 2. Next page — pass the cursor from previous response
GET /v2/fixtures/updates?updated_since=2026-06-05T08:00:00Z&limit=50&cursor=eyJ1Ijoi...abc.d4e5f6

# 3. Last page — no more data
{
  "data": [...12 fixtures...],
  "meta": {
    "next_cursor": null,
    "has_more": false
  }
}
Cursor rules.
• Cursors are opaque — do not parse, modify, or store them long-term.
• Cursors expire after 2 hours. If expired, restart your query without the cursor parameter.
• cursor requires updated_since. Sending cursor alone returns 400.
• cursor and offset cannot be combined. Use offset only for full dataset browsing without updated_since.
• updated_since supports a maximum lookback of 48 hours.

Polling strategy

Use cursor-based polling with updated_since to efficiently fetch only changes since your last sync.

import httpx
import time
from datetime import datetime, timezone

API_URL = "https://api.leaguespy.com/v2/fixtures/updates"
headers = {"X-API-Key": "YOUR_KEY"}
last_check = datetime.now(timezone.utc).isoformat()

while True:
    params = {"limit": 100, "updated_since": last_check}
    cursor = None

    while True:  # Page through all results
        if cursor:
            params["cursor"] = cursor
        resp = httpx.get(API_URL, headers=headers, params=params, timeout=30)
        data = resp.json()

        for fixture in data["data"]:
            process_fixture(fixture)

        if not data["meta"]["has_more"]:
            break
        cursor = data["meta"]["next_cursor"]

    last_check = datetime.now(timezone.utc).isoformat()
    time.sleep(300)  # Poll every 5 minutes
Recommended intervals. Professional tier: 5–10 minutes (data has 3h delay). Real-time tier: 1–5 minutes. Always handle 429 with exponential backoff.

Pagination

When meta.total exceeds your limit, increment offset to fetch remaining pages.

import httpx

API_URL = "https://api.leaguespy.com/v2/fixtures/updates"
headers = {"X-API-Key": "YOUR_KEY"}

all_fixtures = []
offset = 0

while True:
    resp = httpx.get(API_URL, headers=headers, timeout=30,
                     params={"limit": 100, "offset": offset})
    page = resp.json()
    all_fixtures.extend(page["data"])

    # Stop when we've fetched everything
    if offset + page["meta"]["limit"] >= page["meta"]["total"]:
        break
    offset += page["meta"]["limit"]

Changelog

DateVersionChanges
2026-10-272.4.0?include=broadcasts: TV and streaming broadcasters of the fixture by country. Live clock on the fixture: period, minute, extra_minute.
2026-10-212.3.0expected_return on missing[] players (Realtime): estimated return window for injured and suspended players.
2026-10-192.2.0home_score and away_score. lineup_changes: changes in the starting XI against the team's previous match. English originals of all names in *_en on every request. detailed_position, position and reason are always English again; translations moved to *_label.
2026-10-082.1.0New status field (postponed, cancelled, interrupted and other match states); status and kickoff changes update updated_at. Korean localization extended to positions and absence reasons. reason now always comes from a fixed list. Documentation of all response fields refreshed.
2026-06-052.0.4Improved response consistency for fixtures with partial data. Updated API documentation.
2026-01-082.0.2Research team expanded with analysts dedicated to squad rotation during congested schedules.
2025-11-202.0.1Coverage expansion for lower-profile leagues. Improved data freshness for non-top-5 competitions.
2025-09-10—API response time reduced to <120ms (p95). Infrastructure migrated to edge-optimized deployment.
2025-08-15—Match-day squad detection (is_nomination_announced). Improved missing player categorization.
2025-06-20—Expanded filtering options: team_id, league_id and fixture_id query parameters.
2025-03-012.0.0v2 GA release. Tier-based access, structured rate limiting, enriched entity names, updated_since polling.
2024-11-152.0.0-betav2 beta. New response schema, confirmed lineup flow (is_confirmed), nomination tracking.
2024-09-011.4.0Rate limiting rework. Added X-RateLimit-* response headers. Per-key tracking.
2024-09-01—Research team expanded across more time zones. Faster updates throughout the day.
2024-08-15—API infrastructure: gzip compression enabled, significantly reduced payload sizes. Health check endpoint added.
2024-07-101.3.0Tier system introduced (Essential / Professional / Real-Time). Data freshness guarantees per tier.
2024-05-011.2.0Start probability field added. Expected minutes for substitutes — designed for fantasy and DFS platforms.
2024-05-01—Prediction accuracy milestone: significant improvement in starting XI match rate across all covered leagues.
2024-03-151.1.0Formation detection and bench predictions added to response schema.
2024-03-15—Missing player reason enrichment. Detailed categorization: injury type and suspension.
2023-11-011.0.1Pagination support. Stabilized response schema. Added updated_at field.
2023-11-01—Third-tier league coverage: League One & Two (England), 3. Liga (Germany), National (France).
2023-08-10—Prediction accuracy improvements. Better handling of mid-season managerial changes and rotation patterns.
2023-08-011.0.0v1 GA release. Public API with key authentication. 70 competitions covered.
2023-02-01—Second division expansion: Championship, 2. Bundesliga, Serie B, Ligue 2, Segunda División. 50 leagues total.
2022-12-10—API webhook support in private beta. Push-based notifications for lineup changes on selected fixtures.
2022-10-20—South America launch: Brasileirão Série A, Argentine Primera División, Copa Libertadores.
2022-08-01—Asian market entry: J1 League (Japan), K League 1 (South Korea), A-League (Australia).
2022-04-15—Research team expanded with dedicated lineup analysts for every covered league.
2022-01-10—Coverage expanded to 35 leagues. Added Eredivisie, Primeira Liga, Süper Lig, Belgian Pro League, and Scottish Premiership.
2021-11-01—API uptime reaches 99.95% SLA. Migrated to high-availability cloud infrastructure.
2021-09-01—Research team expanded with match preparation specialists.
2021-06-15—5 additional leagues added. Total coverage: 25 competitions across 12 countries.
2021-02-01—JSON response format stabilized. API key authentication introduced.
2020-10-15—Research team expanded with squad specialists. Squad roles tracked for every covered team.
2020-08-01—Predicted bench added to lineup data. Expanded missing player reasons with detailed categories.
2020-05-10—Research team expanded to 40 football experts worldwide. Enhanced coverage depth for mid-table clubs.
2020-02-01—Improved injury and absence tracking. More detailed absence categories.
2019-09-15—Research team expanded. Absence impact assessed for every missing player.
2019-06-01—Coverage expanded to 25 leagues. Added MLS, Liga MX, Russian Premier League, and Swiss Super League.
2019-01-10—Research team scaled to 20 analysts. Improved coverage for lower-profile fixtures.
2018-11-01—API response caching introduced. Average response time dropped from 800ms to under 200ms.
2018-08-15—10 additional leagues added: Eredivisie, Primeira Liga, Austrian Bundesliga, Danish Superliga, and more.
2018-03-01—Notable improvement in prediction accuracy across top 10 leagues after the research team grew.
2017-11-01—Domestic cup coverage added: FA Cup, Copa del Rey, DFB-Pokal, Coppa Italia, Coupe de France.
2017-08-01—Expanded to top 10 European leagues. Added Eredivisie, Primeira Liga, Russian Premier League, Turkish Süper Lig, Belgian Pro League.
2017-02-15—Dedicated research team formed for the top 5 leagues.
2016-11-25—LeagueSpy launched. Top 5 leagues: Premier League, La Liga, Serie A, Bundesliga, Ligue 1. Predicted starting XI.