openfootball / football.json API
openfootball is a free, open, public domain collection of football (soccer) data. The football.json repository provides pre-built JSON files for major leagues and tournaments worldwide. No API key or authentication is required.
Data Sources
There are two ways to access the data:
1. Raw GitHub URLs (Primary)
https://raw.githubusercontent.com/openfootball/football.json/master/{season}/{league}.json
2. GitHub Pages Mirror
https://openfootball.github.io/{country}/{season}/{league-name}.json
Recommendation: Use the raw GitHub URLs for the
football.jsonrepo — they use a simple, consistent naming convention and are the most reliable.
URL Structure
https://raw.githubusercontent.com/openfootball/football.json/master/{season}/{code}.json
| Component | Description | Examples |
|---|---|---|
{season} | Season directory — cross-year or calendar year | 2024-25, 2023-24, 2025, 2019 |
{code} | League code in {country}.{division} format | en.1, de.1, es.1, it.1, fr.1 |
Available Leagues
England
| Code | League | Tier |
|---|---|---|
en.1 | English Premier League | 1st division |
en.2 | English Championship | 2nd division |
en.3 | English League One | 3rd division |
en.4 | English League Two | 4th division |
Germany
| Code | League | Tier |
|---|---|---|
de.1 | Deutsche Bundesliga | 1st division |
de.2 | 2. Bundesliga | 2nd division |
de.3 | 3. Liga | 3rd division |
Spain
| Code | League | Tier |
|---|---|---|
es.1 | Primera División (La Liga) | 1st division |
es.2 | Segunda División | 2nd division |
Italy
| Code | League | Tier |
|---|---|---|
it.1 | Serie A | 1st division |
it.2 | Serie B | 2nd division |
France
| Code | League | Tier |
|---|---|---|
fr.1 | Ligue 1 | 1st division |
fr.2 | Ligue 2 | 2nd division |
Note: Not all leagues are available for all seasons. The
football.jsonrepo is continuously updated — check the repository for the full list of available files.
Available Seasons
Season directories in the football.json repo go back to 2010-11. European leagues use cross-year format (2024-25), while some calendar-year leagues use single-year format (2025).
| Format | Usage | Examples |
|---|---|---|
YYYY-YY | European club seasons (Aug–May) | 2024-25, 2023-24, 2015-16 |
YYYY | Calendar-year competitions | 2025, 2020, 2019 |
Known season directories: 2010-11, 2011-12, 2012-13, 2013-14, 2014-15, 2015-16, 2016-17, 2017-18, 2018-19, 2019-20, 2020-21, 2021-22, 2022-23, 2023-24, 2024-25, 2025-26, 2019, 2020, 2025.
JSON Response Format
All files follow the same JSON schema:
{
"name": "English Premier League 2024/25",
"matches": [
{
"round": "Matchday 1",
"date": "2024-08-16",
"time": "20:00",
"team1": "Manchester United FC",
"team2": "Fulham FC",
"score": {
"ht": [0, 0],
"ft": [1, 0]
}
}
]
}
Top-Level Fields
| Field | Type | Description |
|---|---|---|
name | string | Human-readable league name and season |
matches | array | Array of match objects |
Match Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
round | string | Yes | Round/matchday name (e.g., "Matchday 1", "Round of 16") |
date | string | Yes | Match date in YYYY-MM-DD format |
time | string | No | Kick-off time in HH:MM format (24-hour, local time) |
team1 | string | Yes | Home team name |
team2 | string | Yes | Away team name |
score | object | No | Score object (absent for unplayed future matches) |
status | string | No | Special status (e.g., "awarded" for administratively decided results) |
Score Object Fields
| Field | Type | Description |
|---|---|---|
ft | [int, int] | Full-time score [home, away] |
ht | [int, int] | Half-time score [home, away] (may be absent for some matches) |
Note: Some matches only have
ft(full-time) withoutht(half-time). Always check for the presence ofhtbefore accessing it.
Common Patterns
Fetch a League Season (curl)
curl -s "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json" | jq .
Fetch and Parse Match Data (Python)
import requests
url = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json"
data = requests.get(url).json()
print(f"League: {data['name']}")
print(f"Total matches: {len(data['matches'])}")
for match in data["matches"][:10]:
ft = match.get("score", {}).get("ft")
if ft:
print(f" {match['date']} {match['team1']} {ft[0]}-{ft[1]} {match['team2']}")
else:
print(f" {match['date']} {match['team1']} vs {match['team2']} (no score)")
Build a League Table from Results (Python)
import requests
from collections import defaultdict
url = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json"
data = requests.get(url).json()
table = defaultdict(lambda: {"played": 0, "won": 0, "drawn": 0, "lost": 0,
"gf": 0, "ga": 0, "points": 0})
for match in data["matches"]:
score = match.get("score", {}).get("ft")
if not score:
continue
t1, t2 = match["team1"], match["team2"]
g1, g2 = score
for team, gf, ga in [(t1, g1, g2), (t2, g2, g1)]:
table[team]["played"] += 1
table[team]["gf"] += gf
table[team]["ga"] += ga
if gf > ga:
table[team]["won"] += 1
table[team]["points"] += 3
elif gf == ga:
table[team]["drawn"] += 1
table[team]["points"] += 1
else:
table[team]["lost"] += 1
# Sort by points, then goal difference
sorted_table = sorted(table.items(),
key=lambda x: (x[1]["points"], x[1]["gf"] - x[1]["ga"]),
reverse=True)
print(f"{'Team':<35} {'P':>3} {'W':>3} {'D':>3} {'L':>3} {'GF':>4} {'GA':>4} {'GD':>4} {'Pts':>4}")
print("-" * 70)
for i, (team, stats) in enumerate(sorted_table, 1):
gd = stats["gf"] - stats["ga"]
print(f"{i:>2}. {team:<32} {stats['played']:>3} {stats['won']:>3} "
f"{stats['drawn']:>3} {stats['lost']:>3} {stats['gf']:>4} "
f"{stats['ga']:>4} {gd:>+4} {stats['points']:>4}")
Filter Matches by Team (Python)
import requests
url = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json"
data = requests.get(url).json()
team = "Arsenal FC"
matches = [m for m in data["matches"]
if team in (m["team1"], m["team2"]) and m.get("score", {}).get("ft")]
for m in matches:
ft = m["score"]["ft"]
opponent = m["team2"] if m["team1"] == team else m["team1"]
venue = "H" if m["team1"] == team else "A"
my_goals = ft[0] if m["team1"] == team else ft[1]
opp_goals = ft[1] if m["team1"] == team else ft[0]
result = "W" if my_goals > opp_goals else ("D" if my_goals == opp_goals else "L")
print(f" {m['date']} ({venue}) {result} {my_goals}-{opp_goals} vs {opponent}")
Fetch Multiple Leagues (Python)
import requests
leagues = {
"Premier League": "en.1",
"Bundesliga": "de.1",
"La Liga": "es.1",
"Serie A": "it.1",
"Ligue 1": "fr.1",
}
season = "2024-25"
base = "https://raw.githubusercontent.com/openfootball/football.json/master"
for name, code in leagues.items():
url = f"{base}/{season}/{code}.json"
resp = requests.get(url)
if resp.status_code == 200:
data = resp.json()
total = len(data["matches"])
played = sum(1 for m in data["matches"] if m.get("score", {}).get("ft"))
print(f"{name}: {played}/{total} matches played")
else:
print(f"{name}: not available for {season}")
Fetch and Parse (Node.js)
const url = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json";
const res = await fetch(url);
const data = await res.json();
console.log(`League: ${data.name}`);
console.log(`Matches: ${data.matches.length}`);
data.matches.slice(0, 10).forEach((m) => {
const ft = m.score?.ft;
if (ft) {
console.log(` ${m.date} ${m.team1} ${ft[0]}-${ft[1]} ${m.team2}`);
}
});
Fetch and Parse (bash + jq)
# Get all results for a specific team
curl -s "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json" \
| jq -r '.matches[]
| select(.team1 == "Liverpool FC" or .team2 == "Liverpool FC")
| select(.score.ft)
| "\(.date) \(.team1) \(.score.ft[0])-\(.score.ft[1]) \(.team2)"'
Compare Head-to-Head Results (Python)
import requests
url = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json"
data = requests.get(url).json()
team_a = "Arsenal FC"
team_b = "Liverpool FC"
h2h = [m for m in data["matches"]
if {m["team1"], m["team2"]} == {team_a, team_b}
and m.get("score", {}).get("ft")]
for m in h2h:
ft = m["score"]["ft"]
ht = m["score"].get("ht", ["?", "?"])
print(f"{m['date']}: {m['team1']} {ft[0]}-{ft[1]} {m['team2']} (HT: {ht[0]}-{ht[1]})")
Aggregate Stats Across Seasons (Python)
import requests
base = "https://raw.githubusercontent.com/openfootball/football.json/master"
seasons = ["2022-23", "2023-24", "2024-25"]
team = "Manchester City FC"
all_results = {"W": 0, "D": 0, "L": 0, "GF": 0, "GA": 0}
for season in seasons:
resp = requests.get(f"{base}/{season}/en.1.json")
if resp.status_code != 200:
continue
data = resp.json()
for m in data["matches"]:
ft = m.get("score", {}).get("ft")
if not ft:
continue
if m["team1"] == team:
gf, ga = ft
elif m["team2"] == team:
ga, gf = ft
else:
continue
all_results["GF"] += gf
all_results["GA"] += ga
if gf > ga:
all_results["W"] += 1
elif gf == ga:
all_results["D"] += 1
else:
all_results["L"] += 1
print(f"{team} across {', '.join(seasons)}:")
print(f" W{all_results['W']} D{all_results['D']} L{all_results['L']}")
print(f" Goals: {all_results['GF']} scored, {all_results['GA']} conceded")
Find High-Scoring Matches (bash + jq)
# Find all matches with 5+ total goals in the Premier League 2024/25
curl -s "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json" \
| jq -r '.matches[]
| select(.score.ft)
| select((.score.ft[0] + .score.ft[1]) >= 5)
| "\(.date) \(.team1) \(.score.ft[0])-\(.score.ft[1]) \(.team2) (Total: \(.score.ft[0] + .score.ft[1]))"'
Get Results for a Specific Matchday (Python)
import requests
url = "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json"
data = requests.get(url).json()
matchday = "Matchday 38"
matches = [m for m in data["matches"] if m["round"] == matchday]
print(f"--- {matchday} ---")
for m in matches:
ft = m.get("score", {}).get("ft")
if ft:
print(f" {m['date']} {m.get('time', '')} {m['team1']} {ft[0]}-{ft[1]} {m['team2']}")
else:
print(f" {m['date']} {m.get('time', '')} {m['team1']} vs {m['team2']}")
Other Repositories (Source Data)
The football.json files are auto-generated from plain-text Football.TXT source files in country-specific repos:
| Repo | Content | GitHub URL |
|---|---|---|
england | EPL, Championship, League One, League Two | openfootball/england |
deutschland | Bundesliga, 2. Bundesliga, 3. Liga, DFB Pokal | openfootball/deutschland |
espana | La Liga, Segunda División | openfootball/espana |
italy | Serie A, Serie B, Coppa Italia | openfootball/italy |
france | Ligue 1, Ligue 2 | openfootball/europe (in /europe) |
worldcup | FIFA World Cup (2022, 2018, 2014, etc.) | openfootball/worldcup |
euro | Euro 2024, 2020, 2016, etc. | openfootball/euro |
champions-league | UCL & Europa League | openfootball/champions-league |
clubs | Club & stadium metadata | openfootball/clubs |
world | Leagues from N. America, Asia, Africa, Australia | openfootball/world |
Country repos also have their own GitHub Pages JSON mirrors. For example:
https://openfootball.github.io/england/2024-25/1-premierleague.json
You can convert Football.TXT source files to JSON yourself using the fbtxt2json CLI tool:
# Convert a single league file
fbtxt2json england/2025-26/1-premierleague.txt -o en.1.json
# Convert an entire country repo at once
fbtxt2json . -o ./_site
Rate Limits
GitHub does not publish specific rate limits for raw content, but general guidelines:
| Guideline | Recommendation |
|---|---|
| Polling interval | ≥ 60 seconds between requests for the same file |
| Concurrent requests | Keep reasonable (< 20 concurrent) |
| Caching | Cache responses locally — data changes infrequently |
| Unauthenticated GitHub API | 60 requests/hour per IP (only applies to API endpoints, not raw content) |
Tip: Since match data doesn't change after a game is completed, you can aggressively cache historical seasons. Only poll the current season for updates.
Error Handling
| HTTP Status | Meaning | Action |
|---|---|---|
| 200 | Success | Parse the JSON |
| 404 | File not found | Check the season, league code, or URL spelling |
| 429 | Rate limited | Back off and retry after a delay |
| 5xx | Server error | Retry with exponential backoff |
Tips
- No auth needed — all data is fully public. Start fetching immediately.
- Check for
scorebefore accessing — future/unplayed matches won't have ascorefield. - Check for
htseparately — some matches haveftbut nohtdata. - Team names include suffixes — e.g.,
"Arsenal FC","Manchester United FC","Borussia Dortmund". Use exact string matching. - Team names vary by league — German teams use German names (
"FC Bayern München"), English teams use English names ("Arsenal FC"). - The
statusfield is rare — it appears on administratively decided matches (e.g.,"awarded"). - Cache aggressively — completed seasons never change. Only the current season gets updates.
- Build tables from the data — the dataset provides raw match results; you compute standings, form, H2H, etc.
- Cross-reference leagues — fetch multiple league files to compare across countries.
- Public domain (CC0) — use the data however you want with no restrictions whatsoever.
- Don't edit the JSON directly — if contributing, edit the Football.TXT source files in the country repos; JSON is auto-generated.
- Time field is local — the
timevalue represents the local kick-off time for the match venue. - Scores are
[home, away]—score.ft[0]is always theteam1(home) goals,score.ft[1]is always theteam2(away) goals.
Changelog
- 0.1.0 — Initial release with league data access, JSON schema documentation, and common usage patterns.