Skip to main content

sportapi-python

Official Python client for the SportAPI Sport Line API — Prematch and Live sports odds, scores and live statistics, with typed models and a demo mode that works without an API key.

License: MIT Python 3.9+ Typed

from sportapi import SportAPI

api = SportAPI()                                  # no key yet? demo data
match = api.event(746146992, "live")
print(match.name, match.score_full)               # Arsenal — Coventry City 3:0
print(match.outcome("Total", "Over 3.5").odds)    # 1.23

The library is free and MIT-licensed. The SportAPI data service it talks to is a commercial product: live data needs a personal API key (how to get one).

Features

  • Every documented Sport Line endpoint: menu, events, the match calendar, event, search, plus the optional sports, countries, tournaments, topmatches, toplist, topchampionships and account.
  • Typed models (dataclasses, py.typed) for sports, countries, tournaments, matches, markets, outcomes, live statistics, sub-events and account keys. The original JSON is kept in .raw.
  • Demo mode: without credentials the client serves the full example responses published in the SportAPI documentation, so you can build and test before you have a key.
  • Documented errors as exceptions: InvalidKeyError, KeyExpiredError, LanguageNotAvailableError, GameFinishedError… mapped from error_code / error_message and from event service messages, not just from HTTP status codes.
  • Odds helpers: find markets and outcomes by name or group_id, decimal odds, blocked selections, oc_pointer lookups, the API's column layout preserved.
  • Polite polling: watch_events() / watch_event() and poll() follow the documented minimum intervals and back off 5 → 10 → 20 → 40 s on temporary failures only.
  • Sync and async clients (SportAPI, AsyncSportAPI) on top of httpx. Python 3.9+.

Install

pip install sportapi

Quick start (60 seconds, no key needed)

With no SPORTAPI_KEY / SPORTAPI_BASE_URL set, the client runs in demo mode and emits one SportAPIDemoWarning, so demo data is never mistaken for live data.

from sportapi import LIVE, SportAPI

with SportAPI() as api:
    # 1. Navigation: sports -> countries -> tournaments that have matches right now
    for sport in api.menu(LIVE)[:3]:
        print(sport.name, sport.counter)

    # 2. Live football matches, grouped by tournament, with a short list of main markets
    for group in api.events(1, LIVE):
        for match in group.matches:
            print(f"{match.timer // 60:>3}' {match.score_full}  {match.name}")

    # 3. One match with every market, live statistics and sub-events
    match = api.event(746146992, LIVE)
    total = match.market("Total")
    for over, under in zip(*total.column_outcomes):   # columns as arranged by the API
        print(over.name, over.odds, "|", under.name, under.odds)
    print(match.stat(29))                             # Stat(id=29, name='Possession %', ...)

Demo mode answers exactly the requests that have a published example response (all in English):

Call Example data
menu("live"), menu("line") full menu snapshots
sports(...), countries(1, ...), tournaments(1, 1, ...) navigation snapshots
events(1, "live"), events(1, "line") Live football (39 matches), Prematch "Top" (50 matches)
event(746146992, "live"), event(730321837, "line") Arsenal — Coventry City (Live), Manchester City — Bournemouth (Prematch)
search("Perth", "live"), search("Manchester", "line") search results
topmatches(...), toplist(1) with or without full=True top selections

odds=False is emulated on top of these files. Any other call raises DemoDataUnavailableError with the list of available calls and how to switch to live data.

Live data

You get a personal base URL and an API key from the SportAPI manager. Set them as environment variables (or pass them to the constructor):

export SPORTAPI_BASE_URL="https://YOUR_API_DOMAIN"
export SPORTAPI_KEY="your-api-key"
from sportapi import SportAPI

api = SportAPI()                     # reads SPORTAPI_KEY and SPORTAPI_BASE_URL
# or: SportAPI(api_key="...", base_url="https://YOUR_API_DOMAIN", lang="en")

print(api.account().current_key.days_left)
print(api.key_expires)               # from the X-Key-Expires header of the last response
  • The key is sent only in the Package HTTP header — never in the URL — and is hidden from repr() and exception messages. Keep it in environment variables or a secrets manager, and call the API from your backend, not from a browser.
  • Setting only one of the two variables raises ConfigurationError; SportAPI(demo=False) refuses to fall back to demo data.
  • lang is any language enabled for your key. The API supports 69 languages with SportAPI-specific codes (for example ua, cn, br); see Languages.

Step-by-step setup: Authentication and access.

API coverage

Client method Endpoint Documentation
menu(line_type, cybersport=) GET /v1/menu/{type}/{lang} menu
events(sport_id, line_type, tournament_id=0, full_line=, odds=, cybersport=) GET /v1/events/{sportId}/{tournamentId}/sub/50/{type}/{lang} events
events_by_period(sport_id, hours=, days=, tournament_id=, odds=) GET /v1/events/{sportId}/{tournamentId}/sub/50/line/{hours}/{days}/{lang} match calendar
event(game_id, line_type, odds=) GET /v1/event/{gameId}/group/{type}/{lang} event
search(text, line_type) GET /v1/search/{type}/{lang}/{text} search
sports(line_type, cybersport=) GET /v1/sports/{type}/{lang} sports
countries(sport_id, line_type) GET /v1/countries/{sportId}/{type}/{lang} countries
tournaments(sport_id, country_id, line_type, cybersport=) GET /v1/tournaments/{sportId}/{countryId}/{type}/{lang} tournaments
topmatches(line_type, full=, odds=) GET /v1/topmatches/{type}/{lang} topmatches
toplist(sport_id, full=, odds=) GET /v1/toplist/{sportId}/{lang} toplist
topchampionships(line_type) GET /v1/topchampionships/{type}/{lang} topchampionships
account() GET /v1/account account

line_type is "live" or "line" (Prematch); the constants LIVE, LINE and PREMATCH are exported. lang defaults to the client's lang ("en"). AsyncSportAPI has the same methods as coroutines.

Helpers: iter_live_matches(), watch_events(), watch_event(), iter_matches(), poll() / apoll(), recommended_interval(), and icon URLs in sportapi.media (media assets).

Working with odds

match = api.event(730321837, "line")

w1 = match.outcome("1X2", "W1")          # case-insensitive market and outcome names
w1.odds, w1.odds_decimal                 # 1.525, Decimal('1.525')
w1.blocked                               # oc_block: True means unavailable
w1.pointer                               # oc_pointer: '730321837|1|1|0'

match.market(id=17)                      # by group_id (names are localised)
match.find_markets("Fouls")              # several markets can share a name
match.outcome_by_pointer(w1.pointer)     # track a selection across refreshes
[o.size_value for o in match.market("Total").outcomes]   # oc_size as float (str or int in JSON)
  • events returns a short market list per match (Market.column_outcomes is None); event returns all markets with columns already arranged and sorted by the API.
  • Market sets differ by sport: don't assume 1X2 or a draw exists (tennis has none).
  • oc_pointer identifies a selection for bet placement with the SportAPI Coupon API; see Bet pointer.

Data models: match · odds · live statistics · sub-events · field reference.

Polling and update intervals

The API is REST: each response is a snapshot. The documented minimum intervals are built in:

Data Live Prematch
menu 20 s 60 s
sports, countries, tournaments 60 s 120 s
events 7 s 30 s
event 5 s 30 s
topmatches 30 s 120 s
toplist — 120 s
topchampionships 60 s 300 s
match calendar — 60 s
from sportapi import LIVE, GameFinishedError, SportAPI

api = SportAPI()
try:
    for match in api.watch_event(746146992, LIVE):     # every 5 s, never overlapping
        print(match.score_full, match.timer // 60)
except GameFinishedError:
    print("No longer in the line under this game_id: refresh the match list")

watch_* refuses intervals below the documented minimum. Temporary failures (network, HTTP 5xx) back off 5 → 10 → 20 → 40 s; key, language, sport and parameter errors are raised, not retried. Update a displayed match clock locally from timer instead of polling every second. Details: Data update guidelines.

Errors

SportAPIError
├── ConfigurationError, DemoDataUnavailableError
├── TransportError                       network error or timeout (retryable)
├── UnexpectedResponseError
│   └── HTTPStatusError                  HTTP 4xx/5xx without error_code (5xx retryable)
├── APIError                             error_code + error_message
│   ├── AuthenticationError              MissingKeyError, InvalidKeyError, KeyExpiredError,
│   │                                    KeyBlockedError, KeyNotActiveError, AccessRestrictedError
│   ├── PermissionDeniedError            AccessDeniedError, LanguageNotAvailableError
│   └── InvalidRequestError              InvalidLanguageError, InvalidLineTypeError
└── EventUnavailableError                GameNotFoundError, GameFinishedError

An empty list is not an error: nothing is available for that request right now. See Error handling.

Examples

All examples run in demo mode out of the box:

Script What it shows
examples/live_board.py Live scoreboard with clock and main odds; --watch refreshes at the documented interval
examples/match_odds.py Every market of one match in columns, blocked selections, live stats, sub-events
examples/search_matches.py Search by team name, then open the first match's main markets
examples/menu_tree.py Sports → countries → tournaments with counters and icon URLs
python examples/live_board.py
python examples/match_odds.py 730321837 --line

Documentation

Get an API key

Live data requires a personal base URL and API key. Plans start from $30/month, and there is a free 2-day trial.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md. Please never include an API key in an issue, log or test fixture.

License

MIT © 2026 SportAPI

Metadata

Release files for sportapi 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sportapi 0.1.0
File Size Uploaded
sportapi-0.1.0.tar.gz 142.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sportapi 0.1.0
File Interpreter ABI Platform
sportapi-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 296.6 kB

Release files / sportapi-0.1.0.tar.gz

Download URL sportapi-0.1.0.tar.gz
Size 142.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9027475dfb1b44ca87aea4468469d78e19736434611d967db5d0fd9210a80a98
BLAKE2b-256 checksum
How to use checksums
0c5383bf4e4628e02d23c65b268690522e0e0d669ce730604dc21659be33ebf2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / sportapi-0.1.0-py3-none-any.whl

Download URL sportapi-0.1.0-py3-none-any.whl
Size 154.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7e7831daac548936ee10549d58f66e50461f85628ae651969e6821692b100a99
BLAKE2b-256 checksum
How to use checksums
193a385ba5610d5beeb9765a418835016ac49b547853f5aabf924672357fa854
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page