Live sports scores in Python
Four short scripts on one helper module: live games for any league, a season schedule, one game's details, box score and plays, and a watcher that polls and prints only score changes. For one league in depth, the NFL, NBA, NHL, soccer and college football guides go further.
- Stack
- Python 3.10+, requests
- Plan
- Free to build and try; Starter to poll one team's games all season
- Code
- python/ (MIT)
Updated 2026-11-06. Tested against the live API on that date. Data is aggregated from public sources and is typically 20-30 seconds behind live play (about 1 second after our source). There is no SLA.
1. Get a free API key
Sign up (no card) and copy the key from the dashboard. The Free plan gives you 1,000 calls in the first 30 days, then 125 a month, at 1 request per second. Every successful REST call counts as one call.
Check the key works with one call to the live-scores endpoint:
Terminal
export REALTIME_SPORTS_API_KEY=your_key
curl -H "Authorization: Bearer $REALTIME_SPORTS_API_KEY" \
"https://www.realtimesportsapi.com/api/v1/sports/football/leagues/nfl/events/live"2. Get the code
Everything below is in the python/ folder of the examples repo (MIT). Clone it and work from that folder.
Terminal
git clone https://github.com/ElcoDevRepos/realtime-sports-api-examples
cd realtime-sports-api-examples/python3. Install
Python 3.10+ and requests.
Terminal
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt4. The helper
api_get sends your key as a Bearer token, passes keyword arguments as query parameters and raises with the API's own error message and hint on a failed call. format_game prints one line per game with the event id in brackets.
python/rsa_common.py
"""Shared helpers for the Python examples (uses `requests`)."""
from __future__ import annotations
import os
import sys
from pathlib import Path
from typing import Any, Dict, Optional
import requests
API_BASE = os.environ.get("RSA_API_BASE", "https://www.realtimesportsapi.com/api/v1")
# Load ../.env (KEY=value lines) if present; variables already in the environment win.
_env = Path(__file__).resolve().parent.parent / ".env"
if _env.exists():
for line in _env.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip().strip("'\""))
class ApiError(Exception):
def __init__(self, status: int, body: Optional[Dict[str, Any]], retry_after: Optional[str]):
err = (body or {}).get("error") or {}
self.status = status
self.code = err.get("code")
self.hint = err.get("hint")
self.retry_after = retry_after
super().__init__(f"HTTP {status} {self.code or ''}: {err.get('message', 'request failed')}")
def require_key() -> str:
key = os.environ.get("REALTIME_SPORTS_API_KEY", "").strip()
if not key or key == "your_api_key_here":
sys.exit(
"Set REALTIME_SPORTS_API_KEY first (export it, or copy .env.example to .env).\n"
"Get a free key at https://www.realtimesportsapi.com/signup"
)
return key
def api_get(path: str, **query: Any) -> Dict[str, Any]:
"""GET a path under /api/v1 and return the envelope {success, data, meta}. Raises ApiError on non-2xx."""
key = require_key()
params = {k: v for k, v in query.items() if v is not None}
res = requests.get(API_BASE + path, params=params, headers={"Authorization": f"Bearer {key}"}, timeout=30)
try:
body = res.json()
except ValueError:
body = None
if not res.ok:
raise ApiError(res.status_code, body, res.headers.get("Retry-After"))
return body
def format_game(g: Dict[str, Any]) -> str:
"""One-line score: 'KC 14 @ BAL 10 5:23 - 2nd Quarter [eventId]'"""
a, h = g.get("awayTeam") or {}, g.get("homeTeam") or {}
def side(t: Dict[str, Any]) -> str:
name = t.get("abbreviation") or t.get("name") or "?"
return name if t.get("score") is None else f"{name} {t['score']}"
return f"{side(a)} @ {side(h)} {(g.get('status') or {}).get('detail', '')} [{g.get('id')}]"
def run(main) -> None:
"""Run main() and print API errors cleanly instead of a traceback."""
try:
main()
except ApiError as e:
print(e, file=sys.stderr)
if e.hint:
print(f"Hint: {e.hint}", file=sys.stderr)
if e.status == 429:
print(f"Monthly quota exhausted. Retry after {e.retry_after or '?'} s or upgrade.", file=sys.stderr)
sys.exit(1)
except requests.RequestException as e:
sys.exit(f"Network error: {e}")
5. Games in progress right now
One call. At 2:49 pm Central on 6 November 2026, python live_scores.py soccer eng.1 printed COV 0 @ EVE 0 HT [401879242]. Sport and league slugs: football nfl, basketball nba, hockey nhl, baseball mlb, soccer eng.1, football college-football.
python/live_scores.py
"""Games in progress right now for one league.
Usage: python live_scores.py [sport] [league]
python live_scores.py football nfl
python live_scores.py soccer eng.1 # soccer is "soccer"; "football" is American football
"""
import sys
from rsa_common import api_get, format_game, run
def main() -> None:
sport = sys.argv[1] if len(sys.argv) > 1 else "football"
league = sys.argv[2] if len(sys.argv) > 2 else "nfl"
body = api_get(f"/sports/{sport}/leagues/{league}/events/live")
games = body.get("data") or []
if not games:
print(f"No {league} games live right now.")
for g in games:
print(format_game(g))
remaining = ((body.get("meta") or {}).get("rateLimit") or {}).get("remaining")
if remaining is not None:
print(f"\nCalls remaining this month: {remaining}")
if __name__ == "__main__":
run(main)
6. Schedule and one game
python schedule.py football nfl 2026 9 listed the 15 week-9 games with UTC kickoff times and event ids (2026-11-06T01:15Z JAX 33 @ BAL 10 Final [401873037]). event_details.py takes a sport, league and event id and makes three calls: the game, its box score and the latest plays.
python/schedule.py
"""Season schedule. NFL and college football also take a week.
Usage: python schedule.py [sport] [league] [season] [week] [seasonType]
python schedule.py football nfl 2026 5 2 # 2026 regular season, week 5
python schedule.py hockey nhl 2026
"""
import sys
from rsa_common import api_get, format_game, run
def main() -> None:
args = sys.argv[1:] + [None] * 5
sport, league, season = args[0] or "football", args[1] or "nfl", args[2] or "2026"
week, season_type = args[3], args[4] or "2"
body = api_get(f"/sports/{sport}/leagues/{league}/seasons/{season}/schedule", week=week, seasonType=season_type)
games = body.get("data") or []
for g in games:
print(f"{g.get('date', '')} {format_game(g)}")
print(f"\n{len(games)} games")
if __name__ == "__main__":
run(main)
python/event_details.py
"""One game: details, box score and the latest plays (3 API calls).
Usage: python event_details.py <sport> <league> <eventId>
Find event ids with live_scores.py (printed in [brackets]).
"""
import sys
from rsa_common import api_get, format_game, run
def main() -> None:
if len(sys.argv) < 4:
sys.exit("Usage: python event_details.py <sport> <league> <eventId> (e.g. football nfl 401772982)")
sport, league, event_id = sys.argv[1:4]
base = f"/sports/{sport}/leagues/{league}/events/{event_id}"
print(format_game(api_get(base)["data"]))
box = api_get(f"{base}/boxscore")["data"] or {}
print("\nBox score keys:", ", ".join(box.keys()))
plays = api_get(f"{base}/plays", limit=10)["data"] or []
print("\nPlays:")
for p in plays:
print(" -", p.get("text") or " ".join(x for x in [(p.get("type") or {}).get("text") or "Play", (p.get("clock") or {}).get("displayValue")] if x))
if __name__ == "__main__":
run(main)
7. Watch a league and print score changes
Each poll is one call: about 360 for a 3-hour game at 30 seconds. Try it on Free; Starter (10,000 calls a month) follows one team all season. Data is typically 20-30 seconds behind live play, so faster polling buys nothing, and it is not for in-play betting. On a paid plan, webhooks push score changes to you instead.
Terminal
python live_scores.py hockey nhl
python schedule.py football nfl 2026 9
python event_details.py soccer eng.1 401879242
python watch.py basketball nba 30python/watch.py
"""Poll the live endpoint and print only what changed: score, clock/period, final.
Usage: python watch.py [sport] [league] [seconds]
python watch.py basketball nba 30
Every poll is one API call: a 3-hour game polled every 30 s is about 360 calls.
Data is typically 20-30 seconds behind live play; not for in-play betting.
"""
import sys
import time
from datetime import datetime
from rsa_common import api_get, format_game, run
def main() -> None:
sport = sys.argv[1] if len(sys.argv) > 1 else "football"
league = sys.argv[2] if len(sys.argv) > 2 else "nfl"
seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 30
last: dict = {} # event id -> last line printed
while True:
games = api_get(f"/sports/{sport}/leagues/{league}/events/live").get("data") or []
for g in games:
line = format_game(g)
if last.get(g["id"]) != line:
print(f"{datetime.now():%H:%M:%S} {line}", flush=True)
last[g["id"]] = line
if not games and not last:
print(f"No {league} games live right now.", flush=True)
time.sleep(seconds)
if __name__ == "__main__":
run(main)
8. Going further
League guides with real output: NFL, NBA, NHL, soccer and college football. A whole season in pandas: season analysis.
Endpoints used
GET /sports/{sport}/leagues/{league}/events/liveGET /sports/{sport}/leagues/{league}/seasons/{season}/scheduleGET /sports/{sport}/leagues/{league}/events/{eventId}GET /sports/{sport}/leagues/{league}/events/{eventId}/boxscoreGET /sports/{sport}/leagues/{league}/events/{eventId}/plays
Full reference: docs · OpenAPI · what each endpoint returns, by league
FAQ
- Is there a free live sports scores API for Python?
- Yes. A free key (no card) gives 1,000 calls in the first 30 days, then 125 a month, with every endpoint. These scripts need only requests.
- How do I get live scores for one league in Python?
- Call GET /sports/{sport}/leagues/{league}/events/live with your key, for example /sports/basketball/leagues/nba/events/live. It returns only the games in progress.
- How fresh are the scores?
- Typically 20-30 seconds behind live play: fine for dashboards, bots and alerts, not for in-play betting.