Education Access

Real football data for coursework: fixtures, results, standings, in-play statistics, and the odds history that makes a student project more than a table of goals. Granted by a partner institution, per course, for the length of the course.

The figures below are the public baseline. What a course actually gets — deeper history, a higher rate — is agreed per programme to fit what it teaches.

Coverage

130+ competitions — every top flight and the second divisions with real odds, the same as Pro.

Odds depth

19 bookmakers and the full tick-by-tick odds history, as on Ultra. Every price move, pre-match and in-play.

History & rate

Last 12 months of results and odds. 10 requests a minute per account.

How access works

  1. Your institution gives you a course code. Each course has its own; your lecturer or programme office hands it out. Codes are not case-sensitive and hyphens do not matter.
  2. Sign up like anyone else at 5dollarfootballapi.com/signup — email code or Google, no card. You get a working free key straight away, so you can start reading the docs and calling the API while you wait.
  3. Enter the code on your profile page under “Have a course code?”, with the name you enrolled under. If your institution sent you a sign-up link, the code is already filled in.
  4. Your institution approves you. You get an email, and every key on your account switches to Education Access — same key, same base URL, more data and a higher rate limit. Nothing to change in your code.
  5. It runs until the course ends. Then the account returns to the free plan; your keys keep working. Enrolled in the next course? Enter its code.

For institutions

Education Access is set up per partner institution: administrators create courses and approve students from their own back office, lecturers run the courses assigned to them, and staff hold the plan and a key of their own. To bring a course, write to [email protected] with the programme, the expected number of students and the dates.

Any language: it is plain HTTPS and JSON

There is nothing to install. One header carries your key; every endpoint answers with the same JSON envelope. Whatever your course uses — R, JavaScript, Julia, a spreadsheet that can fetch a URL — the calls look like this.

# Your plan and today's usage
curl "https://api.5dollarfootballapi.com/v1/status" \
  -H "Authorization: Bearer $FOOTBALL_API_KEY"

# Every match in play right now, with odds, events and statistics on each row
curl "https://api.5dollarfootballapi.com/v1/fixtures?status=live&include=odds,events,stats" \
  -H "Authorization: Bearer $FOOTBALL_API_KEY"

# A whole season of one league, page by page
curl "https://api.5dollarfootballapi.com/v1/leagues/LEAGUE_ID/fixtures?status=finished&page=1&per_page=100" \
  -H "Authorization: Bearer $FOOTBALL_API_KEY"
{
  "data": [ ... ],
  "pagination": { "page": 1, "per_page": 100, "count": 100, "has_more": true }
}
  • Pagination: keep requesting page + 1 while has_more is true.
  • Rate limit: a 429 comes with a Retry-After header in seconds — wait that long and retry. X-RateLimit-Remaining on every response tells you how much of the minute is left.
  • Errors use the same envelope with an error object; the error reference lists every code.
  • Every endpoint, parameter and response shape is in the endpoint reference.

Getting started in Python

The same calls from Python, with requests and pandas — no SDK needed. Put your key in an environment variable rather than in the notebook — it is yours, and a notebook gets shared.

pip install requests pandas
export FOOTBALL_API_KEY=fb_live_your_key

1. One helper, one session

A session keeps the connection open and the header attached. The helper reads the JSON envelope, backs off on a 429 using the Retry-After header, and raises on anything else.

import os, time, requests

BASE = "https://api.5dollarfootballapi.com/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['FOOTBALL_API_KEY']}"

def get(path, **params):
    """GET one endpoint and return the parsed JSON envelope."""
    while True:
        r = session.get(f"{BASE}{path}", params=params, timeout=30)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        r.raise_for_status()
        return r.json()

print(get("/status")["data"]["plan"])   # 'education' once you are approved

2. Today's fixtures into a DataFrame

List endpoints take a time window of up to 24 hours in unix seconds and return a pagination object; keep going while has_more is true. include=odds expands every row with the Bet365 lines in the same call.

from datetime import datetime, timezone, timedelta
import pandas as pd

def get_all(path, **params):
    """Follow pagination and return every row."""
    rows, page = [], 1
    while True:
        env = get(path, page=page, per_page=100, **params)
        rows += env["data"]
        if not env["pagination"]["has_more"]:
            return rows
        page += 1

start = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
fixtures = get_all("/fixtures",
                   start_time=int(start.timestamp()),
                   end_time=int((start + timedelta(days=1)).timestamp()),
                   include="odds")

df = pd.json_normalize(fixtures)
print(df[["id", "league.name", "home.name", "away.name", "kickoff_utc", "status"]].head())

3. A season, a team, a table

leagues = pd.json_normalize(get_all("/leagues"))
epl = int(leagues.loc[leagues["name"] == "England Premier League", "id"].iloc[0])

season = pd.json_normalize(get_all(f"/leagues/{epl}/fixtures", status="finished"))
table  = pd.json_normalize(get("/standings", league=epl)["data"])
last10 = pd.json_normalize(get_all(f"/teams/{int(season['home.id'].iloc[0])}/fixtures"))[:10]

4. The odds history — the interesting part

Every recorded price change for one market of one fixture, oldest first, with the score at that moment. This is what Education Access adds over the free plan, and it is the raw material for most of the project ideas below.

fixture_id = int(df["id"].iloc[0])
ticks = pd.json_normalize(get_all(f"/fixtures/{fixture_id}/odds/history", market="asian"))
ticks["time"] = pd.to_datetime(ticks["time"], utc=True)
print(ticks[["time", "line", "home", "away", "score"]].tail())

# Other bookmakers, one at a time — see /v1/bookmakers for the slugs:
pinnacle = get_all(f"/fixtures/{fixture_id}/odds/history", market="1x2", bookmaker="pinnacle")

5. Be kind to your rate limit

  • Cache what does not change. A finished match and its odds history never change again. Save responses to disk (df.to_parquet, or requests-cache) and read from there while you iterate.
  • Use the batch call. /fixtures?include=odds,events,stats is one request for a whole day, not one per match.
  • Read the headers. X-RateLimit-Remaining tells you how much of the minute is left; the helper above already waits out a 429.
  • Pull once, analyse many times. A notebook that re-downloads a season on every cell run spends the whole class's patience. Download, save, then work on the file.

Project ideas

Analysis projects on real match data — results, events, statistics, corners, cards, and the odds history most free sources do not have. Each is a question and a starting point; each fits a notebook, a Streamlit or Dash app, or a large-format visual.

Home advantage

How big is home advantage, league by league, and is it shrinking? Points per game and goal difference at home versus away across a season, then across the seasons you can reach.

When goals happen

Use the event timeline to map goals by minute. Which leagues score late, how often does a half-time lead survive, and what does a comeback look like in the numbers?

Corner profiles

Corners for and against by team, first half versus second half, home versus away. Build each team's corner profile for the season and see which styles of play it reveals.

Cards and what follows

Bookings by minute and by league from the event timeline. After a red card, what happens to goals, corners and the final result for the team that lost a player?

Market efficiency

Are opening odds or closing odds the better forecast? Turn both into probabilities, score them against a season of results, and see where and when the market learns.

Model backtest

Build a match-outcome model from results and statistics, then backtest it on a season you held out. How does it compare with the probabilities the market published before kickoff?

Live dashboard

A Streamlit page that polls status=live once a minute and shows every match in play with score, corners, cards and in-play statistics. Small, real, and demo-able.

Publishing your work

Please do. Repositories, blog posts, dashboards and portfolio pieces are exactly what the programme is for. Two things to keep in mind:

  • Credit the data. Education Access is a $0 plan, so anything public-facing — a website, app, bot, notebook on GitHub or published dataset — carries “Football data by 5DollarFootballAPI”, linked to the site. A README line or a footer is fine. Formats.
  • Publish your analysis, not the feed. Charts, models, aggregates, samples that illustrate your work — all fine. The raw data as a bulk download or a competing feed is the one thing the terms do not allow, on any plan.