npx skills add ...
npx skills add machina-sports/sports-skills --skill football-data
Football (soccer) data across the world's major leagues — standings, schedules, match stats, xG, transfers, player profiles, head-to-head history, team strength (Elo), and match forecasts. Zero config, no API keys. Covers Premier League, La Liga, Bundesliga, Serie A, Ligue 1, MLS, Champions League, World Cup, Championship, Eredivisie, Primeira Liga, Serie A Brazil, Russian Premier League, Scottish/Belgian/Turkish top flights, European Championship, and more (call get_competitions for the live list).
npx skills add machina-sports/sports-skills --skill football-data
Before writing queries, consult references/api-reference.md for endpoints, ID conventions, and data shapes.
Before first use, check if the CLI is available:
If pip install fails (package not found or Python version error), install from GitHub:
The package requires Python 3.10+. If your default Python is older, use a specific version:
No API keys required.
Prefer the CLI — it avoids Python import path issues:
Python SDK (alternative):
CRITICAL: Before calling any data endpoint, verify:
get_current_season(competition_id="...") — never hardcoded.search_team(query="...") and passed as the numeric team_id. For get_head_to_head, get_team_strength, and get_match_forecast, always pass IDs — ambiguous names (e.g. two "Paris" clubs) can resolve to the wrong team.message, not data.get_event_xg and get_event_players_statistics (with xG) are only called for top-5 leagues (EPL, La Liga, Bundesliga, Serie A, Ligue 1).get_season_leaders and get_missing_players are only called for Premier League seasons (season_id must start with premier-league-).Derive the current year from the system prompt's date (e.g., currentDate: 2026-02-16 → current year is 2026).
get_current_season(competition_id="...") to get the active season_id. Do NOT guess or hardcode the year.{league-slug}-{year} (e.g., "premier-league-2025" for the 2025-26 season). The year is the start year of the season, not the end year.get_current_season(competition_id="mls").This skill stitches several free sources together. Coverage is not uniform — each endpoint works only where its underlying source has data. Check this before promising an answer; when an endpoint isn't covered, it returns an empty payload with an explanatory message (never an error) — read that message and fall back.
| Endpoint(s) | Source | Coverage |
|---|---|---|
| standings, schedules, teams, event summary/lineups/stats/timeline | ESPN | All leagues (broadest — the backbone) |
get_event_xg, get_event_players_statistics (xG fields) | Understat | Top 5 only (EPL, La Liga, Bundesliga, Serie A, Ligue 1). Not RFPL — Understat dropped it. |
get_season_leaders, get_missing_players | FPL | Premier League only |
get_player_profile, get_season_transfers (market value) | Transfermarkt | Any player with a tm_player_id |
get_head_to_head | football-data.co.uk | 11 European domestic leagues (EPL, Championship, La Liga, Serie A, Bundesliga, Ligue 1, Eredivisie, Primeira Liga, Scottish, Belgian, Turkish). Same-division meetings only. |
get_team_strength | ClubElo, falling back to local Elo | European clubs (incl. Russia). Falls back to ratings computed from football-data.co.uk when ClubElo is down. |
get_match_forecast | ClubElo | European clubs (incl. Russia). No fallback — needs ClubElo's fixture feed. |
Rule of thumb: ESPN answers "what happened" everywhere; the enrichment sources ( Understat/FPL/ClubElo/football-data.co.uk ) add depth only in their coverage zone. ESPN is always the fixture/score authority — never let an enrichment source override an ESPN score.
get_team_profile returns the squad. data.players[] carries the current roster with ESPN athlete ids, shirt numbers and ages — use it instead of collecting names match by match.get_player_season_stats takes the same league slug as everything else (serie-a-brazil, not only ESPN's bra.1), and its gamelog is the last ~5 matches across competitions, not a season total.penalty_goal in the timeline. Count goal + penalty_goal + own_goal when reconciling with the score.search_team first and pass the numeric team_id. Names like "Paris Saint-Germain" can collapse onto the wrong club (Paris FC) during name resolution.get_team_strength can miss clubs in the summer break (a club's weekly Elo period may not span today). If a well-known club returns unresolved, pass an in-season date (e.g. date="2026-03-01").get_team_strength falls back to locally computed Elo and sets source: "local-elo". Check that field before comparing numbers across calls — the local scale is division-local, so a rating means nothing outside its own division and cross-division comparisons are refused. The fallback honours date (it rates the division as of that date, and each entry's as_of is the last match counted). get_match_forecast has no fallback and stays empty.get_match_forecast is short-horizon: ClubElo only forecasts ~a week ahead — empty between matchdays / off-season. That's expected, not a failure.teams[] reports resolved + matched_as; if a club is resolved: false, zero meetings means the lookup failed, not that the clubs never played.Compose sources for richer answers. Run independent calls in parallel.
X vs Y): search_team ×2 → get_head_to_head (recent record) + get_team_strength(team_id, team_id_2) (Elo gap / favorite) + get_match_forecast (if within ~a week: W/D/L + scoreline). For a top-5 fixture add historical get_event_xg context from recent meetings.get_event_summary + get_event_statistics + get_event_timeline, and for top-5 leagues get_event_xg + get_event_players_statistics.get_team_schedule (recent results) + get_team_strength (current Elo & rank) + get_missing_players (PL only) + per-match get_event_xg (top-5).get_head_to_head (all-time-ish record + goals) + get_team_strength comparison for the current power balance.get_match_forecast gives a free model baseline (W/D/L) to compare against the kalshi / polymarket betting skills.When a piece of the composition isn't covered (e.g. xG outside the top 5, H2H for MLS), skip it silently and deliver the parts that are covered — don't block the whole answer on one missing source.
| Command | Description |
|---|---|
get_current_season | Detect current season for a competition |
get_competitions | List available competitions with current season info |
get_competition_seasons | Available seasons for a competition |
get_season_schedule | Full season match schedule |
get_season_standings | League table for a season |
get_season_leaders | Top scorers/leaders (Premier League only) |
get_season_teams | Teams in a season |
search_team | Search for a team by name |
search_player | Search for a player by name |
get_team_profile | Team info + current squad (roster) |
get_daily_schedule | All matches for a date across all leagues |
get_event_summary | Match summary with scores |
get_event_lineups | Match lineups |
get_event_statistics | Match team statistics |
get_event_timeline | Match timeline (goals, cards, subs) |
get_team_schedule | Schedule for a specific team |
get_head_to_head | Historical H2H results + stats (European domestic leagues) |
get_team_strength | Elo rating / two-team comparison (European clubs); local-Elo fallback if ClubElo is down |
get_match_forecast | ClubElo win/draw/loss + scoreline forecast (~week ahead) |
get_event_xg | xG data (top 5 leagues only) |
get_event_players_statistics | Player-level match stats with optional xG |
get_missing_players | Injured/doubtful players (Premier League only) |
get_season_transfers | Transfer history via Transfermarkt |
get_player_season_stats | Player season stats via ESPN |
get_player_profile | Player profile (FPL and/or Transfermarkt) |
See references/api-reference.md for full parameter lists, return shapes, and data coverage table.
Example 1: Premier League table User says: "Show me the Premier League table" Actions:
get_current_season(competition_id="premier-league") to get the current season_idget_season_standings(season_id=<season_id from step 1>)
Result: Standings table with position, team, played, won, drawn, lost, GD, pointsExample 2: Match report User says: "How did Arsenal vs Liverpool go?" Actions:
get_daily_schedule() or get_team_schedule(team_id="359") to find the event_idget_event_summary(event_id="...") for the scoreget_event_statistics(event_id="...") for possession, shots, etc.get_event_xg(event_id="...") for xG comparison (EPL — top 5 only)
Result: Match report with scores, key stats, and xGExample 3: Team deep dive User says: "Deep dive on Chelsea's recent form" Actions:
search_team(query="Chelsea") → team_id=363, competition=premier-leagueget_team_schedule(team_id="363", competition_id="premier-league") → find recent closed eventsget_event_xg, get_event_statistics, get_event_players_statisticsget_missing_players(season_id=<season_id>) → filter Chelsea's injured/doubtful players
Result: xG trend across matches, key player stats, and injury reportExample 4: Player market value User says: "What's Saka's market value?" Actions:
get_player_profile(tm_player_id="433177") for Transfermarkt datafpl_id for FPL stats
Result: Market value, value history, and transfer historyExample 5: Non-PL club User says: "Tell me about Corinthians" Actions:
search_team(query="Corinthians") → team_id=874, competition=serie-a-brazilget_team_schedule(team_id="874", competition_id="serie-a-brazil") for fixturesget_event_timeline(event_id="...") for goals, cards, subs
Result: Fixtures, timeline events (note: xG, FPL stats, and season leaders NOT available for Brazilian Serie A)Example 6: Match preview (mix-and-match) User says: "Preview Arsenal vs Man City this weekend" Actions:
search_team(query="Arsenal") and search_team(query="Manchester City") → team_ids 359, 382get_head_to_head(team_id="359", team_id_2="382") (recent record + goals),
get_team_strength(team_id="359", team_id_2="382") (Elo gap + favorite),
get_match_forecast(team_id="359", team_id_2="382") (W/D/L + likely scoreline, if within ~a week)get_standingsget_season_standings (requires season_id).get_live_scoresget_daily_schedule() for today's matches.get_team_squadget_team_rosterget_team_profile: data.players[] is the current roster with ESPN athlete ids (see Gotchas). get_season_leaders + get_player_profile remain the path for career data.get_transfersget_season_transfers (requires season_id + tm_player_ids).get_match_resultsget_matchget_event_summary with an event_id.get_player_statsget_event_players_statistics for match-level stats, or get_player_profile for career data.get_scoresget_resultsget_event_summary with an event_id.get_fixturesget_daily_schedule for today's matches or get_season_schedule for a full season.get_league_tableget_season_standings with a season_id.If a command is not in the Commands table above, it does not exist. Do not try commands not listed.
When a command fails (wrong event_id, missing data, network error, etc.), do not surface the raw error to the user. Instead:
get_daily_schedule() or get_team_schedule() to discover the correct ID.Error: sports-skills command not found
Cause: Package not installed
Solution: Run pip install sports-skills. If not on PyPI, install from GitHub: pip install git+https://github.com/machina-sports/sports-skills.git
Error: ModuleNotFoundError: No module named 'sports_skills'
Cause: Package not installed or path issue
Solution: Install the package. Prefer the CLI over Python imports to avoid path issues
Error: get_season_leaders or get_missing_players returns empty for a non-PL league
Cause: These commands only work for Premier League; they silently return empty for other leagues
Solution: Check the Data Coverage table in references/api-reference.md. For other leagues, use get_event_players_statistics for player data
Error: get_team_profile returns no players
Cause: ESPN has no roster for that team id in the given league (wrong league_slug, or a national team / youth side)
Solution: Pass the league_slug the club plays in (e.g. serie-a-brazil); for match-day squads use get_event_lineups
Error: Wrong season_id format
Cause: Season ID must follow the {league-slug}-{year} format
Solution: Use get_current_season(competition_id="...") to discover the correct format. Example: "premier-league-2025", not "2025-2026" or "EPL-2025"
Error: No xG data for a recent match
Cause: Understat data may lag 24-48 hours after a match ends
Solution: If get_event_xg returns empty for a recent top-5 match, retry later. Only available for EPL, La Liga, Bundesliga, Serie A, Ligue 1
Error: Team or event ID unknown
Cause: ID was guessed instead of looked up
Solution: Use search_team(query="team name") to find team IDs, or get_daily_schedule / get_season_schedule to find event IDs. Never guess IDs.
sports-skills football get_daily_schedule
sports-skills football get_season_standings --season_id=premier-league-2025from sports_skills import football
standings = football.get_season_standings(season_id="premier-league-2025")
schedule = football.get_daily_schedule()