Skip to main content
Teamarr uses a priority-based provider system to fetch sports data. When resolving events or teams for a league, providers are tried in priority order — the one with the lowest priority number is used first. All providers implement the SportsProvider interface and are registered in teamarr/providers/__init__.py.

Provider chain

Request for league data


  ProviderRegistry.get_for_league(league)

        ├── ESPN (priority 0)         → supports? → Yes → use ESPN
        ├── MLB Stats (priority 40)   → supports? → Yes → use MLB Stats
        ├── HockeyTech (priority 50)  → supports? → Yes → use HockeyTech
        └── TSDB (priority 100)       → supports? → Yes → use TSDB

Provider summary

ProviderPriorityLeagues coveredAuthRate limit
ESPN052 pre-configured + 240+ soccerNone (public API)Generous (DNS is usually the bottleneck)
MLB Stats405 (MiLB)None (public API)None observed
HockeyTech5014Public client keysNone observed
TSDB10011API key in URL path30/min free, 100/min premium

SportsProvider interface

All providers implement these methods, defined in teamarr/core/interfaces.py:
MethodRequiredDescription
nameYesProvider identifier (e.g., "espn")
supports_league(league)YesWhether this provider handles the given league code
get_events(league, date)YesAll events for a league on a specific date
get_team_schedule(team_id, league, days_ahead)YesUpcoming schedule for a specific team
get_team(team_id, league)YesTeam details by ID
get_event(event_id, league)YesSingle event by ID
get_team_stats(team_id, league)NoDetailed team statistics
get_league_teams(league)NoAll teams in a league (used by cache refresh)
get_supported_leagues()NoList of supported league codes

League mapping format

Each league in schema.sql maps to a provider via the provider and provider_league_id columns. The format of provider_league_id varies by provider:
ProviderFormatExample
ESPNsport/leaguefootball/nfl, soccer/eng.1
MLB Statssport_id11 (Triple-A)
HockeyTechclient_codeohl, ahl
TSDBleague_id4460 (IPL)

Design principles

No direct DB access

Providers receive configuration via dependency injection at instantiation time. Database access stays at the factory boundary in teamarr/providers/__init__.py.

Lazy initialization

Providers are created on-demand via factory functions. A provider is only instantiated when first needed.

Thread-safe HTTP clients

All providers use connection pooling with configurable limits to handle concurrent requests safely.

Exponential backoff

All providers retry with jitter on failure. Base delay 0.5s, capped at 10s. Rate limit (429) responses use a longer backoff: 5s base, capped at 60s, respecting Retry-After headers.

Provider registration

Providers are registered in teamarr/providers/__init__.py using ProviderRegistry.register(). This is the single place where providers are configured:
ProviderRegistry.register(
    name="espn",
    provider_class=ESPNProvider,
    factory=_create_espn_provider,
    priority=0,
    enabled=True,
)
To add a new provider:
  1. Create a provider module under teamarr/providers/newprovider/.
  2. Register it in teamarr/providers/__init__.py with a factory function and priority.
  3. Add league configuration to the leagues table in teamarr/database/schema.sql.
The rest of the system automatically discovers and uses registered providers.

Build docs developers (and LLMs) love