openfootball
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.
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "openfootball"
description: "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."
license: "Apache-2.0"
---
# openfootball / football.json API
[openfootball](https://github.com/openfootball) is a free, open, public domain collection of football (soccer) data. The [football.json](https://github.com/openfootball/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.json` repo — 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.json` repo is continuously updated — check the [repository](https://github.com/openfootball/football.json) 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:
```json
{
"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) without `ht` (half-time). Always check for the presence of `ht` before accessing it.
---
## Common Patterns
### Fetch a League Season (curl)
```bash
curl -s "https://raw.githubusercontent.com/openfootball/football.json/master/2024-25/en.1.json" | jq .
```
### Fetch and Parse Match Data (Python)
```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)
```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)
```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)
```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)
```javascript
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)
```bash
# 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)
```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)
```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)
```bash
# 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)
```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](https://github.com/openfootball/england) |
| `deutschland` | Bundesliga, 2. Bundesliga, 3. Liga, DFB Pokal | [openfootball/deutschland](https://github.com/openfootball/deutschland) |
| `espana` | La Liga, Segunda División | [openfootball/espana](https://github.com/openfootball/espana) |
| `italy` | Serie A, Serie B, Coppa Italia | [openfootball/italy](https://github.com/openfootball/italy) |
| `france` | Ligue 1, Ligue 2 | [openfootball/europe](https://github.com/openfootball/europe) (in `/europe`) |
| `worldcup` | FIFA World Cup (2022, 2018, 2014, etc.) | [openfootball/worldcup](https://github.com/openfootball/worldcup) |
| `euro` | Euro 2024, 2020, 2016, etc. | [openfootball/euro](https://github.com/openfootball/euro) |
| `champions-league` | UCL & Europa League | [openfootball/champions-league](https://github.com/openfootball/champions-league) |
| `clubs` | Club & stadium metadata | [openfootball/clubs](https://github.com/openfootball/clubs) |
| `world` | Leagues from N. America, Asia, Africa, Australia | [openfootball/world](https://github.com/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:
```bash
# 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 `score` before accessing** — future/unplayed matches won't have a `score` field.
- **Check for `ht` separately** — some matches have `ft` but no `ht` data.
- **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 `status` field 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 `time` value represents the local kick-off time for the match venue.
- **Scores are `[home, away]`** — `score.ft[0]` is always the `team1` (home) goals, `score.ft[1]` is always the `team2` (away) goals.
---
## Changelog
- **0.1.0** — Initial release with league data access, JSON schema documentation, and common usage patterns.More General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

