Skip to contents

Every season of the CBA data site, newest first (the upcoming season is the default), 1995-96 on.

Endpoint: GET https://data-server.cbaleague.com/api/com-code-tables/getSeason

Usage

cba_seasons()

Value

A hoopR_data tibble: One row per season: label, START year, default flag. An empty valid answer is a zero-row tibble with these columns, in this order (sportsdataverse-py cba_seasons):

col_nametypedescription
is_defaultintegerDefault-selection flag: 1 = the site's default season (exactly one row: the season in progress or next), 2 = not the default.
keycharacterSeason label 'YYYY-YYYY' (START year - END year), e.g. '2025-2026'; the sdv season key is the second year. The list runs from the upcoming season (2026-2027, 32 rows) down to 1995-1996 with no gap.
valuecharacterSeason START year (2025 = the 2025-26 season). The sdv season key is the END year = season + 1; map at the boundary. String of digits ('2025'); the value every season parameter takes.

Details

  • Unofficial and unsupported: these wrappers read the routes that cbaleague.com's own data site (a Vue single-page app) calls. The site's terms were not reviewed; check them before you republish the data. Requests are spaced at least 1 s apart per host; keep use small.

  • Access: every response body is AES-128-ECB encrypted with a public key shipped in the site's front-end bundle; the runtime decrypts it with digest::AES() (digest is a suggested package: install it to use these wrappers) and refuses anything that does not decrypt. The host blocks datacenter IPs at TCP: set the environment variable CBA_PROXY (or options(hoopR.proxy = ...)) to a residential proxy URL; it is never printed or put in a condition. Without a proxy the request goes direct and, from a datacenter, fails within seconds (a 5 s connect timeout, no retry of a refused or timed-out connect). Where direct access works (a residential IP), set CBA_DIRECT=1 for the full 60 s timeout and retries.

  • Seasons: the API's season is the START year; every season argument here is the END year (2026 = 2025-26) and is sent as season - 1. Columns named season keep the wire value (the START year, so the sdv END year is season + 1). The season list reaches 1995-96; box data before 2009-10 is unverified.

  • Coverage: game wrappers serve FINISHED games only. Each first reads the game report header (cached per session), and an unfinished game raises hoopR_asset_fetch. Box, period, roster and best-player bodies are checked against that header (points sum to the score, team ids match, advanced rows are box players). Play-by-play is served one period per request: every period is fetched, and a partial set, an unordered clock or a final score the plays do not reach is refused. Play-by-play is verified on 2017-18 and 2025-26 only and is absent for 2009-10 (hoopR_no_data). There are NO shot x/y coordinates in this API (shot location exists only as team and player zone splits), and play-by-play has no player or team ids (Chinese sentence text plus surnames). Box data has 12-minute quarters in 2009-10 and 2017-18 and 10-minute quarters in 2025-26. The women's CBA is not covered (no official source).

  • Not game data: cba_game_team_shots() and cba_game_team_four() answer the two teams' SEASON tables (the shot zones are the teams' current profile; the four factors are the previous season), whichever game is asked for. The reference lists (cba_team_options(), cba_team_counts()) and cba_report_status() are not echoed by the API, so their scope is unchecked.

  • Duplicate rows: in some games (2025-26 game 100105799) the advanced-box and extras routes repeat player rows (26 rows for 12 players), and the repeats are not always copies. cba_player_advanced() and cba_player_extras() keep the FIRST row per player_id.

  • Text fields are Chinese (names, play-by-play sentences, position and nationality labels). The column descriptions here are ASCII; sportsdataverse-py keeps the full English glossaries of the codes.

  • Personal data: the operator ids (createdBy / lastModifiedBy, one holds a mobile number) and any bare mobile-number string are removed from every body. Players' public bio fields (birth date, height, weight) are kept.

  • Ids: schedule_id (games, one global 9-digit sequence 100xxxxxx), team_id (clubs, stable across sponsor renames), player_id (persons) and match_type_id are integers. The roster registration id of cba_rosters() is a 19-digit integer that an R integer or double cannot hold exactly, so it is kept as its exact digits (character).

  • Errors are classed conditions: hoopR_no_data (HTTP 404, a game without play-by-play), hoopR_bad_request (a non-integer id or season, HTTP 400 / 422) and hoopR_asset_fetch (any other non-200, including HTTP 500, which this API answers for a body it dislikes and which is not retried; a dead connection; an empty or undecryptable body; an unfinished game; a body for another game, team, season or day), all inheriting hoopR_error. HTTP 429 / 502 / 503 / 504 are retried, up to three attempts. Wrappers raise; they never warn and return empty.

Examples

# \donttest{
  try(cba_seasons())
#> ── CBA Seasons from data-server.cbaleague.com ─────────────────── hoopR 3.2.0 ──
#> ℹ Data updated: 2026-10-31 21:27:47 UTC
#> # A tibble: 32 × 3
#>    is_default key       value
#>         <int> <chr>     <chr>
#>  1          1 2026-2027 2026 
#>  2          2 2025-2026 2025 
#>  3          2 2024-2025 2024 
#>  4          2 2023-2024 2023 
#>  5          2 2022-2023 2022 
#>  6          2 2021-2022 2021 
#>  7          2 2020-2021 2020 
#>  8          2 2019-2020 2019 
#>  9          2 2018-2019 2018 
#> 10          2 2017-2018 2017 
#> # ℹ 22 more rows
# }