Open-source project
pseudo-r/Public-ESPN-API avatar
pseudo-r/Public-ESPN-API

412 v2 routes counted from a WADL, not from live probes

The Public ESPN API documentation provides a complete list of undocumented API endpoints, URL parameters, and JSON response formats across 20+ sports (NFL, NBA, MLB, NHL). Includes live curl examples, routing guides, and a Django REST API service for live scores, standings, and news. Optimized for search, developers, AI, and LLMs.

753 stars90 forksPythonMIT

At a glance

What is it?
pseudo-r/Public-ESPN-API is an inventory of ESPN's undocumented endpoints, refreshed on 2026-09-30, together with a Django service that caches them. The scope note is the part to read first: reachability is not permission, and the project says so before it says anything else.
Who is it for?
Use this repository if you are building a scoreboard, standings or news view on ESPN data and can live with an interface that may change without notice and a route list that is partly unverified. Do not use it to republish ESPN content at scale, because the scope file says reachability is not permission to collect, reuse or redistribute, and the page carries no rate limits because none are published.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The route counts are read out of a WADL, and the page says so

The headline coverage line reads 17 configured sports, 356 Core-discovered league references, 412 v2 routes and 97 v3 routes, and it is followed by a parenthesis that qualifies it: the figures were refreshed on 2026-09-30, WADL-advertised routes are not all live-verified, and the mapping comes from `application.wadl` on the v2 and v3 Core domains. That distinction decides how much weight the numbers carry. A WADL is a machine-readable description of what a service advertises, so a route can be listed and still fail, and the page does not claim otherwise. The verification work lives in separate files instead, including live probe results, a league snapshot and an audit dated the same day.

NHL standings answer on /apis/v2/, and the site path returns a stub

Four curl lines make up the quick start, and the fourth one carries a warning in its comment that is the most useful thing in the block. Team and scoreboard routes sit under `/apis/site/v2/sports/...`, but NHL standings have to be fetched from `/apis/v2/sports/hockey/nhl/standings`, because the site-prefixed form of that same route returns a stub rather than data. Nothing in the response tells you which one you got; you get a successful HTTP call either way, and an empty payload is the only symptom.

bash
# NFL Scoreboard
curl "https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard"

# NBA Teams
curl "https://site.api.espn.com/apis/site/v2/sports/basketball/nba/teams"

# MLB Scores for a Specific Date
curl "https://site.api.espn.com/apis/site/v2/sports/baseball/mlb/scoreboard?dates=20260929"

# NHL Standings — NOTE: use /apis/v2/ (not /apis/site/v2/ which returns a stub)
curl "https://site.api.espn.com/apis/v2/sports/hockey/nhl/standings"

Core responses hand out .pvt URLs that only work after a rewrite

Some Core API responses carry `$ref` URLs pointing at `sports.core.api.espn.pvt`, which the page identifies as ESPN's internal domain and states is not publicly accessible. The fix is a one-part substitution: replace `.pvt` with `.com` and the reference resolves, so `sports.core.api.espn.pvt/v2/...` becomes `sports.core.api.espn.com/v2/...`. The same section covers the more ordinary failure modes: these endpoints are not officially supported and may change without notice, most need no authentication at all, and no rate limits are published even though excessive requests may be blocked. The advice that follows is to implement caching and error handling in whatever you build on top.

Soccer season dates and the days that actually have fixtures

The endpoint notes flag a mismatch that costs people an afternoon. Dates returned by the seasons and calendar endpoint, such as the start and end of a soccer season, do not always line up with when scoreboard data exists. If a scoreboard comes back empty for a date the calendar advertises as in season, the answer is not a bug in your code: the fix is to query a date range closer to when matches actually begin. That is filed as issue #17, alongside the `.pvt` rewrite as issue #20, and both entries point at the same pattern, which is that ESPN's own metadata describes a schedule its data endpoints have not populated yet.

Cricket is a documented sport with zero leagues

Each sport in the coverage table gets its own reference document, a slug, and a league count taken from the Core catalog. The counts are uneven in ways that tell you where the real density is: MMA shows 48, rugby 25, basketball 15 and baseball 12, while golf shows 9, hockey 6, football 5, racing 5, lacrosse 4, and Australian football and field hockey 1 each. Cricket sits in the same table with 0 leagues and a reference document like the rest, which is the case to notice: a documented sport is not the same as a documented sport with any competition behind it. Anyone scoping a project from the sport list has to read the league column, not just check the sport is present.

A Django service with Postgres, Redis, a worker and a beat scheduler

The repository is not only prose. Alongside `docs/`, `scripts/` and the policy files sits `espn_service/`, a Django project, and the compose file brings up five services from it. The database is `postgres:16-alpine` with health checks, the broker is `redis:7-alpine` with its own health check, the web container runs migrations and the Django server, and there are separate Celery worker and Celery beat containers. `nhl_service/` sits at the root as a second service directory. The compose file publishes both backend ports to the host:

yaml
services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: espn_db
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432:5432"
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
  worker:
    command: celery -A config worker --loglevel=info
  beat:
    command: celery -A config beat --loglevel=info

The environment example ships DEBUG on and a placeholder secret

Copying the environment example is the documented first step, and what it contains is honest about being a starting point rather than a deployment. `SECRET_KEY` is the literal string change-me-to-a-long-random-string, `DEBUG` is on, and `ALLOWED_HOSTS` is limited to localhost and the loopback address. The database URL and both Celery URLs point at the same local Postgres and Redis the compose file starts, and three ESPN client settings are spelled out as a timeout of 30, 3 retries, and a user agent string. Five base URL variables exist so each domain can be overridden without touching code.

bash
cp .env.example espn_service/.env

That default user agent is worth changing before you make many calls, since the page asks you to be considerate about request volume while telling you that no official limits exist to know.

The scope note comes before the endpoint list, on purpose

The first block on the page is a scope statement, not a pitch. The repository holds endpoint documentation and reference code, is not affiliated with or endorsed by the upstream providers, and offers no hosted API or data service. The word public in the name is defined there as describing endpoint reachability, not permission to collect, reuse or redistribute data, with a project scope file linked for the permitted-use detail. There is also a source verification section stating that the project distributes no API-key installers and no desktop application, and that similarly named websites and their download links are not project releases, backed by a security file and a report about a third-party download concern. No GitHub releases exist, so `CHANGELOG.md` is the only release record.

Editorial conclusion

Use this repository if you are building a scoreboard, standings or news view on ESPN data and can live with an interface that may change without notice and a route list that is partly unverified. Do not use it to republish ESPN content at scale, because the scope file says reachability is not permission to collect, reuse or redistribute, and the page carries no rate limits because none are published. Before you build on it, probe the specific routes you need against the live-probe results and the audit file, confirm your league's sport slug appears in the coverage table with a nonzero league count, and decide early whether you are scraping or using the bundled Django service, since the compose defaults there are development values rather than deployment ones.

Frequently asked questions

Does ESPN have a public API?

ESPN runs undocumented APIs that power its website and mobile apps, and this repository documents them. Most endpoints are publicly accessible with no authentication, while the page is explicit that they are not officially supported and may change without notice.

How many endpoints does the Public ESPN API document?

412 v2 routes and 97 v3 routes, alongside 356 Core-discovered league references across 17 configured sports, refreshed on 2026-09-30. The page states that WADL-advertised routes are not all live-verified and that the mapping is read from `application.wadl` on the v2 and v3 Core domains.

What is the best free API for sports data?

The project makes no comparison between providers. What it offers is an inventory of ESPN's own undocumented endpoints, with no authentication needed on most of them, live probe results and an audit file you can use to check which routes actually answered.

Is there an API for ESPN Fantasy Football?

One of the documented domains is `fantasy.espn.com` on v3, listed for fantasy sports leagues, and the contents page carries a Fantasy Sports API section. The page does not state whether private league data can be read without the league owner's credentials.

How do I run the Django service that ships with the Public ESPN API?

Copy `.env.example` to `espn_service/.env` and use the compose file, which starts Postgres, Redis, a web container that runs migrations and the Django server, plus a Celery worker and a Celery beat scheduler. The web container serves on port 8000 and needs both backing services to report healthy first.

How can I make my private ESPN league viewable to the public?

That is outside what this repository covers, which is the ESPN side of the data rather than league sharing. Its scope note says the project is not affiliated with or endorsed by the upstream providers and offers no hosted service, so league visibility settings are a question for ESPN itself.

Official sources

  1. Issues
  2. Project website
  3. pseudo-r/Public-ESPN-API on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pseudo-r-public-espn-api.svg)](https://hysenlabs.com/projects/pseudo-r-public-espn-api)