SportAPI Documentation
EN
S Product documentationSport Line API
v1
Service & pricing ↗ Get access ↗
Sport Line API / Python

Sport Line API — Python Example

Requirements

This example requires Python 3.10 or later and uses only the standard library. No additional packages are required.

Check the version:

python3 --version

Where to Make Requests

This example is intended for a backend application. Do not pass the Sport Line API key to code running in a browser or mobile application, where a user could retrieve it.

If the user interface needs the data, it should call the client’s backend, and the backend should request Sport Line API.

Environment Variables

The code uses two variables:

VariableValue
SPORTAPI_BASE_URLBase URL received from the SportAPI manager
SPORTAPI_PACKAGE_KEYPersonal API key

Do not write an active key directly into the source code or commit a secrets file to a public repository.

Minimal Request

import json
import os
from urllib.request import Request, urlopen

base_url = os.environ["SPORTAPI_BASE_URL"].rstrip("/")
api_key = os.environ["SPORTAPI_PACKAGE_KEY"]

request = Request(
    f"{base_url}/v1/menu/live/en",
    headers={
        "Package": api_key,
        "Accept": "application/json",
    },
    method="GET",
)

with urlopen(request, timeout=15) as response:
    payload = json.load(response)

print(payload)

The API key is sent in the Package HTTP header, not in the URL.

Shared Request Function

The following function:

  • adds the Package header;
  • limits the response wait time;
  • parses JSON;
  • checks the HTTP status;
  • recognizes error_code and error_message;
  • validates the standard status, page, and body envelope.
import json
import os
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


class SportApiError(RuntimeError):
    def __init__(
        self,
        message,
        *,
        code=None,
        http_status=None,
        payload=None,
    ):
        super().__init__(message)
        self.code = code
        self.http_status = http_status
        self.payload = payload


raw_base_url = os.environ.get("SPORTAPI_BASE_URL")
api_key = os.environ.get("SPORTAPI_PACKAGE_KEY")

if not raw_base_url:
    raise RuntimeError("SPORTAPI_BASE_URL is not configured")

if not api_key:
    raise RuntimeError("SPORTAPI_PACKAGE_KEY is not configured")

base_url = raw_base_url.rstrip("/")


def sport_api_get(path):
    request = Request(
        f"{base_url}{path}",
        headers={
            "Package": api_key,
            "Accept": "application/json",
        },
        method="GET",
    )

    try:
        with urlopen(request, timeout=15) as response:
            http_status = response.status
            response_text = response.read().decode("utf-8")
    except HTTPError as error:
        http_status = error.code
        response_text = error.read().decode("utf-8", errors="replace")
    except URLError as error:
        raise SportApiError(
            f"Could not connect to Sport Line API: {error.reason}"
        ) from error

    try:
        payload = json.loads(response_text)
    except json.JSONDecodeError as error:
        raise SportApiError(
            f"Sport Line API returned invalid JSON. HTTP {http_status}",
            http_status=http_status,
        ) from error

    if isinstance(payload, dict) and "error_code" in payload:
        raise SportApiError(
            f"Sport Line API error {payload['error_code']}: "
            f"{payload.get('error_message', 'Unknown error')}",
            code=payload["error_code"],
            http_status=http_status,
            payload=payload,
        )

    if http_status >= 400:
        raise SportApiError(
            f"Sport Line API returned HTTP {http_status}",
            http_status=http_status,
            payload=payload,
        )

    if not (
        isinstance(payload, dict)
        and "status" in payload
        and isinstance(payload.get("page"), str)
        and "body" in payload
    ):
        raise SportApiError(
            "Unknown Sport Line API response format",
            http_status=http_status,
            payload=payload,
        )

    return payload

Check for API errors in the JSON as well. Do not determine the request result using only the HTTP status.

Classifying body

In standard methods, body contains a data array or object. In event, it may also contain a service message.

def classify_body(payload):
    body = payload["body"]

    if isinstance(body, dict) and isinstance(body.get("message"), str):
        return {
            "type": "message",
            "message": body["message"],
        }

    if isinstance(body, list) and not body:
        return {
            "type": "empty",
            "data": [],
        }

    return {
        "type": "data",
        "data": body,
    }

Possible results:

typeMeaning
dataThe API returned method data
emptyNo data is currently available for this selection
messageevent returned Game not found or Game id finished

Selecting a Current Branch from menu

The following function selects the first sport, country, and tournament present in the current menu. In a real interface, the user selects the required branch.

def find_first_tournament(menu_body):
    for sport in menu_body:
        for country in sport.get("sub", []):
            for tournament in country.get("sub", []):
                return {
                    "sport_id": sport["id"],
                    "sport_name": sport["name"],
                    "country_id": country["id"],
                    "country_name": country["name"],
                    "tournament_id": tournament["id"],
                    "tournament_name": tournament["name"],
                }

    return None

The IDs are not hard-coded. The function retrieves them from the current menu response.

Selecting a Match from an events Response

events groups matches by tournament. To retrieve the first available match, iterate through events_list:

def find_first_game(events_body):
    for tournament in events_body:
        events_list = tournament.get("events_list", [])

        if events_list:
            return events_list[0]

    return None

Complete menu → events → event Example

Save the following code as sportapi_example.py:

import json
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen


class SportApiError(RuntimeError):
    def __init__(
        self,
        message,
        *,
        code=None,
        http_status=None,
        payload=None,
    ):
        super().__init__(message)
        self.code = code
        self.http_status = http_status
        self.payload = payload


raw_base_url = os.environ.get("SPORTAPI_BASE_URL")
api_key = os.environ.get("SPORTAPI_PACKAGE_KEY")

if not raw_base_url:
    raise RuntimeError("SPORTAPI_BASE_URL is not configured")

if not api_key:
    raise RuntimeError("SPORTAPI_PACKAGE_KEY is not configured")

base_url = raw_base_url.rstrip("/")
line_type = "live"
language = "en"


def sport_api_get(path):
    request = Request(
        f"{base_url}{path}",
        headers={
            "Package": api_key,
            "Accept": "application/json",
        },
        method="GET",
    )

    try:
        with urlopen(request, timeout=15) as response:
            http_status = response.status
            response_text = response.read().decode("utf-8")
    except HTTPError as error:
        http_status = error.code
        response_text = error.read().decode("utf-8", errors="replace")
    except URLError as error:
        raise SportApiError(
            f"Could not connect to Sport Line API: {error.reason}"
        ) from error

    try:
        payload = json.loads(response_text)
    except json.JSONDecodeError as error:
        raise SportApiError(
            f"Sport Line API returned invalid JSON. HTTP {http_status}",
            http_status=http_status,
        ) from error

    if isinstance(payload, dict) and "error_code" in payload:
        raise SportApiError(
            f"Sport Line API error {payload['error_code']}: "
            f"{payload.get('error_message', 'Unknown error')}",
            code=payload["error_code"],
            http_status=http_status,
            payload=payload,
        )

    if http_status >= 400:
        raise SportApiError(
            f"Sport Line API returned HTTP {http_status}",
            http_status=http_status,
            payload=payload,
        )

    if not (
        isinstance(payload, dict)
        and "status" in payload
        and isinstance(payload.get("page"), str)
        and "body" in payload
    ):
        raise SportApiError(
            "Unknown Sport Line API response format",
            http_status=http_status,
            payload=payload,
        )

    return payload


def classify_body(payload):
    body = payload["body"]

    if isinstance(body, dict) and isinstance(body.get("message"), str):
        return {"type": "message", "message": body["message"]}

    if isinstance(body, list) and not body:
        return {"type": "empty", "data": []}

    return {"type": "data", "data": body}


def find_first_tournament(menu_body):
    for sport in menu_body:
        for country in sport.get("sub", []):
            for tournament in country.get("sub", []):
                return {
                    "sport_id": sport["id"],
                    "sport_name": sport["name"],
                    "country_id": country["id"],
                    "country_name": country["name"],
                    "tournament_id": tournament["id"],
                    "tournament_name": tournament["name"],
                }

    return None


def find_first_game(events_body):
    for tournament in events_body:
        events_list = tournament.get("events_list", [])

        if events_list:
            return events_list[0]

    return None


def main():
    menu_payload = sport_api_get(f"/v1/menu/{line_type}/{language}")
    menu_result = classify_body(menu_payload)

    if menu_result["type"] == "empty":
        print("No sections are currently available in the selected line.")
        return

    if menu_result["type"] != "data" or not isinstance(
        menu_result["data"], list
    ):
        raise SportApiError("The menu method returned an unexpected body.")

    selection = find_first_tournament(menu_result["data"])

    if selection is None:
        print("No tournament is currently available.")
        return

    print("Selected current navigation branch:", selection)

    events_payload = sport_api_get(
        f"/v1/events/{selection['sport_id']}/{selection['tournament_id']}"
        f"/sub/50/{line_type}/{language}"
    )
    events_result = classify_body(events_payload)

    if events_result["type"] == "empty":
        print("The selected tournament currently has no matches.")
        return

    if events_result["type"] != "data" or not isinstance(
        events_result["data"], list
    ):
        raise SportApiError("The events method returned an unexpected body.")

    game = find_first_game(events_result["data"])

    if game is None:
        print("No match is currently available.")
        return

    print(
        "Selected current match:",
        {
            "game_id": game["game_id"],
            "first_opponent": game.get("opp_1_name"),
            "second_opponent": game.get("opp_2_name"),
        },
    )

    event_payload = sport_api_get(
        f"/v1/event/{game['game_id']}/group/{line_type}/{language}"
    )
    event_result = classify_body(event_payload)

    if event_result["type"] == "message":
        print(f"The match is unavailable: {event_result['message']}")
        return

    if event_result["type"] != "data" or not isinstance(
        event_result["data"], dict
    ):
        raise SportApiError("The event method returned an unexpected body.")

    event = event_result["data"]
    print(
        "Detailed match:",
        {
            "game_id": event["game_id"],
            "first_opponent": event.get("opp_1_name"),
            "second_opponent": event.get("opp_2_name"),
            "outcomes": event.get("game_oc_counter"),
            "subgames": len(event.get("sub_games", [])),
        },
    )


if __name__ == "__main__":
    try:
        main()
    except SportApiError as error:
        print(str(error), file=sys.stderr)

        if error.code is not None:
            print(f"SportAPI error code: {error.code}", file=sys.stderr)

        if error.http_status is not None:
            print(f"HTTP status: {error.http_status}", file=sys.stderr)

        raise SystemExit(1) from error

Run it with:

SPORTAPI_BASE_URL='https://YOUR_API_DOMAIN' \
SPORTAPI_PACKAGE_KEY='YOUR_API_KEY' \
python3 sportapi_example.py

Do not use this form with an active key in shared shell history or during a screen demonstration. In a production environment, store the key in the project’s secrets system.

Retrieving All Tournaments for a Selected Sport

If you do not need to select a particular tournament, pass tournamentId=0:

events_payload = sport_api_get(
    f"/v1/events/{sport_id}/0/sub/50/live/en"
)

You must still retrieve sport_id from the current Live menu.

Requesting a Submatch

After retrieving the main match details, select an item from sub_games:

match = event_result["data"]
subgames = match.get("sub_games", [])

if subgames and subgames[0].get("game_id"):
    subgame_payload = sport_api_get(
        f"/v1/event/{subgames[0]['game_id']}/group/{line_type}/{language}"
    )
    subgame_result = classify_body(subgame_payload)

    if subgame_result["type"] == "data":
        print(
            "Selected subgame:",
            subgame_result["data"].get("game_dop_name"),
        )

The returned odds apply only to the selected submatch.

Always encode user-provided text with quote():

from urllib.parse import quote

search_text = quote("Manchester City", safe="")
search_payload = sport_api_get(
    f"/v1/search/line/en/{search_text}"
)
search_result = classify_body(search_payload)

Do not add an unprocessed user-provided string directly to the URL.

Optional Additional Methods

The following methods are not required for the core integration. Use them only when the interface or project logic needs the corresponding feature.

The recommended core flow remains:

menu → events → event

Purpose of the additional requests:

  • sports, countries, and tournaments provide a step-by-step alternative to menu;
  • topmatches provides a ready-made top-match selection across all sports;
  • toplist provides a Prematch selection for one chosen sport;
  • cybersport=true provides a separate esports selection.

Do not make all these requests automatically simply because the methods exist.

# Live sports
sports = sport_api_get("/v1/sports/live/en")

# Countries for a current sport
countries = sport_api_get(
    f"/v1/countries/{sport_id}/live/en"
)

# Tournaments for a current sport and country
tournaments = sport_api_get(
    f"/v1/tournaments/{sport_id}/{country_id}/live/en"
)

# Live top matches with extended summaries
topmatches = sport_api_get("/v1/topmatches/live/en?full=true")

# Top matches for a selected sport, Prematch only
toplist = sport_api_get(
    f"/v1/toplist/{prematch_sport_id}/en?full=true"
)

# Esports Live menu
cybersport_menu = sport_api_get(
    "/v1/menu/live/en?cybersport=true"
)

Retrieve sport_id, country_id, and prematch_sport_id in these fragments from the current responses for the corresponding sports line type.

Handling Game id finished

payload = sport_api_get(
    f"/v1/event/{game_id}/group/live/en"
)
result = classify_body(payload)

if result["type"] == "message":
    if result["message"] == "Game id finished":
        print("Stop updating this game_id and refresh the Live events list.")
    else:
        print("SportAPI event message:", result["message"])

Game id finished does not indicate whether the match ended, was cancelled, or moved from Prematch to Live. Stop requesting the old ID and refresh events.

An Empty List Is Not an Error

payload = sport_api_get(
    "/v1/events/SPORT_ID_FROM_MENU/0/sub/50/live/en"
)
result = classify_body(payload)

if result["type"] == "empty":
    print("There are currently no matches in this selection.")

Do not replace an empty current response with previously stored matches.

What to Log When an Error Occurs

You may log:

  • request time;
  • method path without the key;
  • HTTP status;
  • error_code and error_message;
  • body.message;
  • IDs and sports line type used.

Do not log the value of the Package header.

What the Example Does Not Provide

This code is not a ready-made SDK and does not implement:

  • periodic updates;
  • automatic retries;
  • application caching;
  • data models;
  • a user interface;
  • a bet placement and settlement system.

Recommended request frequencies are documented separately in Data Update Guidelines.