TUTORIAL

How to Get Football Player Stats via API

Fetch player stats from a football stats API. Python and JavaScript examples for goals, assists, appearances, and more. Free trial included.

8 min read

Player statistics are the backbone of fantasy football apps, scouting tools, analytics dashboards, and sports media products. Goals, assists, appearances, minutes played, cards, shots on target - this data drives everything from player comparison widgets to machine learning models that predict transfer values.

This guide shows you how to fetch football player stats from an API using Python and JavaScript. We will cover searching for players, fetching season stats, bulk-fetching all players in a competition, and caching strategies that keep your app fast without burning through your request quota. All examples use TheStatsAPI, which includes 84,000+ player profiles with detailed statistics across 80 competitions by default (with up to 1,196 available on request).

What Player Stats Are Available

A good football API provides more than just goals and assists. Here is what you can expect from TheStatsAPI's player statistics endpoints:

  • Goals - total goals scored in a season or competition
  • Assists - total assists provided
  • Appearances - number of matches played (starts + substitute appearances)
  • Minutes played - total minutes on the pitch
  • Yellow cards and red cards - disciplinary record
  • Shots and shots on target - shooting volume and accuracy
  • Passes and pass accuracy - distribution metrics
  • Tackles and interceptions - defensive contributions
  • Clean sheets - for goalkeepers and defenders

These stats are broken down by season and competition, so you can track a player's performance across different leagues and time periods. With 10 years of historical data, you can also pull career-spanning statistics for trend analysis.

Fetching Season Stats in Python

Let's start by fetching stats for a specific player. You will need the player's ID, which you can get from the search endpoint (covered in the next section).

import requests

API_KEY = "your_api_key_here"
BASE_URL = "https://api.thestatsapi.com/api"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Accept": "application/json"
}

# Fetch stats for a specific player in a specific season
# Use /football/players?search=Haaland to find the player_id
# Use /football/competitions/{competition_id}/seasons to find the season_id
player_id = "pl_61928103"   # Erling Haaland
season_id = "sn_6125938"    # 2024-25 season

response = requests.get(
    f"{BASE_URL}/football/players/{player_id}/stats",
    headers=headers,
    params={"season_id": season_id}
)

if response.status_code == 200:
    data = response.json()
    stats = data.get("data", {})

    print(f"Player: {stats.get('player_id')}")
    print(f"Season: {stats.get('season_id')}")
    print(f"Position: {stats.get('position', 'N/A')}")
    print(f"Rating: {stats.get('rating', 'N/A')}")
    print()

    scoring = stats.get("scoring", {})
    discipline = stats.get("discipline", {})

    print(f"Apps: {stats.get('appearances', 0)} | Starts: {stats.get('starts', 0)}")
    print(f"Goals: {scoring.get('goals', 0)} | Assists: {scoring.get('assists', 0)}")
    print(f"Minutes: {stats.get('minutes_played', 0)}")
    print(f"YC: {discipline.get('yellow_cards', 0)} | RC: {discipline.get('red_cards', 0)}")

    shooting = stats.get("shooting", {})
    print(f"Shots: {shooting.get('total_shots', 0)} | On target: {shooting.get('shots_on_target', 0)}")
else:
    print(f"Error: {response.status_code} - {response.text}")

The player stats response is organized into categories: scoring (goals, assists), shooting (shots, accuracy), passing, defending, duels, and discipline (cards). Each call returns stats for one season - pass a different season_id to get other seasons.

Searching for a Player by Name

You rarely know a player's ID upfront. The search endpoint lets you find players by name:

import requests

API_KEY = "your_api_key_here"
BASE_URL = "https://api.thestatsapi.com/api"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Accept": "application/json"
}

# Search for a player
search_term = "Haaland"
response = requests.get(
    f"{BASE_URL}/football/players",
    headers=headers,
    params={"search": search_term}
)

if response.status_code == 200:
    data = response.json()
    players = data.get("data", [])

    print(f"Found {len(players)} players matching '{search_term}':\n")

    for player in players:
        pid = player["id"]
        name = player["name"]
        team = player.get("current_team", {}).get("name", "N/A")
        nationality = player.get("nationality", "N/A")
        position = player.get("position", "N/A")
        print(f"  ID: {pid} | {name} | {team} | {position} | {nationality}")
else:
    print(f"Error: {response.status_code}")

The search is case-insensitive and matches against the player's full name. Use the returned id to then fetch that player's detailed statistics.

Combining Search and Stats

Here is a practical function that searches for a player and immediately fetches their stats:

def get_player_stats_by_name(name, season_id):
    """Search for a player and return their season stats."""
    # Step 1: Search
    search_response = requests.get(
        f"{BASE_URL}/football/players",
        headers=headers,
        params={"search": name}
    )
    players = search_response.json().get("data", [])

    if not players:
        print(f"No players found for '{name}'")
        return None

    # Use the first match
    player = players[0]
    player_id = player["id"]
    print(f"Found: {player['name']} (ID: {player_id})")

    # Step 2: Fetch stats for the given season
    stats_response = requests.get(
        f"{BASE_URL}/football/players/{player_id}/stats",
        headers=headers,
        params={"season_id": season_id}
    )
    return stats_response.json().get("data", {})


stats = get_player_stats_by_name("Salah", season_id="sn_6125938")

Bulk Fetching All Players in a Team

If you are building a fantasy app or a comprehensive stats database, you need all players in a team. The players endpoint supports filtering by team and pagination:

import requests
import time

API_KEY = "your_api_key_here"
BASE_URL = "https://api.thestatsapi.com/api"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Accept": "application/json"
}

def fetch_all_players(team_id):
    """Fetch all players in a team, handling pagination."""
    all_players = []
    page = 1

    while True:
        response = requests.get(
            f"{BASE_URL}/football/players",
            headers=headers,
            params={
                "team_id": team_id,
                "page": page
            }
        )

        if response.status_code == 429:
            print("Rate limited - waiting 60 seconds...")
            time.sleep(60)
            continue

        data = response.json()
        players = data.get("data", [])
        all_players.extend(players)

        total_pages = data["meta"]["total_pages"]
        print(f"Page {page}/{total_pages} - {len(all_players)} players so far")

        if page >= total_pages:
            break

        page += 1
        time.sleep(2)  # Respect rate limits

    return all_players


# Fetch all Manchester City players
players = fetch_all_players(team_id="tm_3039")
print(f"\nTotal players: {len(players)}")

This approach respects rate limits by adding a 2-second delay between requests and automatically retries on 429 errors. For the Starter plan (30 requests per minute), this keeps you safely under the limit. To fetch players across an entire competition, first get all teams using /football/teams?competition_id=comp_3039, then fetch players for each team.

JavaScript Example

Here is a complete JavaScript version using fetch in Node.js 18+:

const API_KEY = "your_api_key_here";
const BASE_URL = "https://api.thestatsapi.com/api";

const headers = {
  Authorization: `Bearer ${API_KEY}`,
  Accept: "application/json",
};

async function searchPlayer(name) {
  const url = new URL(`${BASE_URL}/football/players`);
  url.searchParams.set("search", name);

  const response = await fetch(url, { headers });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);

  const data = await response.json();
  return data.data;
}

async function getPlayerStats(playerId, seasonId) {
  const url = new URL(`${BASE_URL}/football/players/${playerId}/stats`);
  url.searchParams.set("season_id", seasonId);

  const response = await fetch(url, { headers });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);

  const data = await response.json();
  return data.data;
}

async function main() {
  // Search for a player
  const results = await searchPlayer("De Bruyne");
  if (results.length === 0) {
    console.log("No players found");
    return;
  }

  const player = results[0];
  console.log(`Found: ${player.name} (ID: ${player.id})\n`);

  // Fetch their stats for a season
  // Get the season_id from /football/competitions/{competition_id}/seasons
  const stats = await getPlayerStats(player.id, "sn_6125938");

  const { scoring, discipline, shooting } = stats;
  console.log(`Season: ${stats.season_id}`);
  console.log(`Apps: ${stats.appearances} | Starts: ${stats.starts}`);
  console.log(
    `Goals: ${scoring.goals} | Assists: ${scoring.assists}`
  );
  console.log(
    `Minutes: ${stats.minutes_played} | YC: ${discipline.yellow_cards} | RC: ${discipline.red_cards}`
  );
  console.log(
    `Shots: ${shooting.total_shots} | On target: ${shooting.shots_on_target}`
  );
}

main().catch(console.error);

Caching Strategies

Player statistics have different freshness profiles. Live match stats can update during supported fixtures, while season totals and historical player aggregates are finalized after full time and typically settle within 1-2 hours. This means aggressive caching is still safe for profiles and season aggregates, while live match views should poll the relevant live endpoints more frequently.

Cache duration recommendations

  • Player search results: Cache for 24 hours. Player names and team affiliations rarely change mid-season.
  • Season statistics: Cache for 6-12 hours during the season, 24+ hours during the off-season. Stats only change after matches, and most leagues play 1-2 matches per week per team.
  • Player profiles (bio, nationality, position): Cache for 7 days. This data almost never changes.

Simple in-memory cache in Python

import time

_cache = {}

def cached_get(url, headers, params=None, ttl=21600):
    """Simple cache wrapper. ttl is in seconds (default 6 hours)."""
    cache_key = f"{url}:{params}"

    if cache_key in _cache:
        cached_at, data = _cache[cache_key]
        if time.time() - cached_at < ttl:
            return data

    response = requests.get(url, headers=headers, params=params)
    data = response.json()
    _cache[cache_key] = (time.time(), data)
    return data

For production applications, use Redis or Memcached instead of an in-memory dictionary. The principle is the same: store the response, check the TTL before making a new request, and invalidate when needed.

Cache math

On the Starter plan (100,000 requests per month), caching player stats with a 6-hour TTL means each player's stats are fetched at most 4 times per day. For 500 players, that is 2,000 requests per day or roughly 60,000 per month - well within your quota, with room for search queries and other endpoints.

Frequently Asked Questions

How many players does TheStatsAPI cover?

TheStatsAPI includes over 84,000 player profiles across 80 competitions by default, with up to 1,196 competitions available on request. This covers all major European leagues, South American competitions, Asian leagues, and many lower divisions. Players are linked to their teams and competition history, so you can track transfers and career trajectories.

Can I get historical player stats from previous seasons?

Yes. TheStatsAPI provides 10 years of historical data, including player statistics from past seasons. You can fetch a player's stats and receive their full career statistical history, broken down by season and competition. This is especially useful for building prediction models and career comparison tools.

Are player stats available during a live match?

Yes. TheStatsAPI provides realtime match stats for supported live fixtures. Use the live match data during play, then use finalized player and match statistics after full time for leaderboards, fantasy scoring, model training, and historical analysis.


TheStatsAPI offers a 7-day free trial on all plans, giving full access to every endpoint, 80 competitions out of the box (with up to 1,196 available on request), and 84,000+ players - making it the best way to evaluate a premium football API before committing. Start your trial at thestatsapi.com and build your player stats integration today.

Start building today

Ready to Power Your Sports App?

Start your 7-day free trial. All endpoints included on every plan.

Cancel anytime
7-day free trial
Setup in 5 minutes