Growing Discord community — direct access to the developer, live coverage & picks. Join the Discord Join now →
Matches Leagues Predictions The Edge Money News Stats Coverage
Docs / More Sports APIs pro

CS2 API#

The CS2 API covers competitive Counter-Strike 2: tournaments, teams (with ELO ratings and map pools), players, matches with the series score and each side's ELO going into the match, per-match pre-match team and player stats, and model-based win predictions.

Base URL: https://sports.bzzoiro.com/csgo/api/v2/

Access#

Requires the Sports Addon ($7/month, $5 Founder rate) — it unlocks the tennis, CS2, darts, hockey, basketball and horse racing APIs plus their MCP servers. Get it at /addons/. You also need a free account token from /register/.

Authorization: Token YOUR_API_KEY

Without a token you get 401:

{"error": "authentication required", "code": "authentication_required",
 "detail": "Send Authorization: Token <YOUR_TOKEN> on every request.",
 "register": "https://sports.bzzoiro.com/register/"}

With a token but no addon you get 402:

{"error": "Sports Addon required", "code": "addon_required",
 "detail": "Tennis, CS:GO, darts, hockey, basketball and horse racing APIs require the Sports Addon ($7/mo, $5 for Founder badge holders).",
 "pricing_url": "https://sports.bzzoiro.com/pricing/"}

List endpoints paginate with limit (default 50, max 200) and offset, and return {count, next, previous, results}.

Endpoints#

Endpoint Description
GET /csgo/api/v2/tournaments/ List tournaments
GET /csgo/api/v2/tournaments/{id}/ Tournament detail
GET /csgo/api/v2/teams/ List/search teams with current ELO
GET /csgo/api/v2/teams/{id}/ Team detail: current ELO, map pool, round winrates
GET /csgo/api/v2/players/ List/search players
GET /csgo/api/v2/players/{id}/ Player profile and current team
GET /csgo/api/v2/matches/ List matches
GET /csgo/api/v2/matches/live/ Matches in play right now
GET /csgo/api/v2/matches/{id}/ Match detail: series score, format, pre-match ELO
GET /csgo/api/v2/matches/{id}/odds/ Per-bookmaker odds, every market
GET /csgo/api/v2/matches/{id}/stats/ Pre-match team form, map pool, head-to-head
GET /csgo/api/v2/matches/{id}/player-stats/ Per-player form with percentiles
GET /csgo/api/v2/predictions/ Model win probabilities
GET /csgo/api/v2/predictions/{id}/ One prediction

The machine-readable schema is public at /csgo/api/schema/, with an interactive UI at /csgo/api/docs/ and a themed explorer at /docs/explorer/csgo/.

Tournaments#

Query parameters

Param Type Description
include_inactive bool Include finished/inactive tournaments
limit / offset int Pagination (default 50, max 200)

Only active tournaments by default. Responses are cached ~5 minutes.

{"count": 403, "next": "...?limit=2&offset=2", "previous": null, "results": [
  {"id": 110, "api_id": 1448, "name": "A1 Gaming League S11", "is_active": true},
  {"id": 149, "api_id": 1646, "name": "Ace North American Masters Spring 2026", "is_active": true}
]}

Teams#

Query parameters

Param Type Description
search string Name search
country string ISO country code
limit / offset int Pagination

Cached ~5 minutes.

curl -H "Authorization: Token YOUR_API_KEY" \
  "https://sports.bzzoiro.com/csgo/api/v2/teams/?search=vitality"
{"count": 2, "next": null, "previous": null, "results": [
  {"id": 464, "name": "Team Vitality", "short_name": "Team Vitality",
   "country": "", "country_code": "", "elo_rating": 1756, "elo_peak": 1756},
  {"id": 943, "name": "Vitality Academy", "short_name": "Vitality. AC",
   "country": "", "country_code": "", "elo_rating": 1510, "elo_peak": 1510}
]}

elo_rating is the team's rating today (1500 = average). For the rating a team held going into a given match, use home_elo_before / away_elo_before on the match. country and country_code are often empty.

GET /teams/{id}/ adds map_stats, from the latest snapshot we hold (as_of is the start of the match that snapshot belongs to; null when we hold none): the team's overall, T-side and CT-side round win rates (%), its overall map win rate, and maps — the maps it picks. Despite its name, maps[].winrate is the share of the team's picks that went to that map (they add up to about 100), not a win rate on the map.

{"id": 464, "name": "Team Vitality", "short_name": "Team Vitality",
 "country": "", "country_code": "", "elo_rating": 1756, "elo_peak": 1756,
 "map_stats": {"as_of": "2026-10-11T12:00:00+00:00",
   "maps": [{"map": "Inferno", "winrate": 20}, {"map": "Nuke", "winrate": 25},
            {"map": "Dust2", "winrate": 35}, {"map": "Cache", "winrate": 20.0},
            {"map": "Anubis", "winrate": 5}],
   "sample_size": 50, "map_winrate": 68.0, "round_winrate": 57.0,
   "round_winrate_t": 51.5, "round_winrate_ct": 62.3}}

Players#

Query parameters

Param Type Description
search string Name/nickname search
team int Filter by team id
limit / offset int Pagination

GET /players/{id}/ returns the player's profile and current team. Per-player performance (K/D, damage, opening kills, clutch rounds) is published per match, at /matches/{id}/player-stats/ (see Match stats).

{"id": 906, "api_id": 108, "nickname": "ZywOo",
 "first_name": "Mathieu", "last_name": "Herbaut",
 "country": "", "country_code": "",
 "current_team": {"id": 464, "name": "Team Vitality", "short_name": "Team Vitality",
                  "country_code": "", "country": ""}}

Matches#

Query parameters

Param Type Description
tournament int Filter by tournament id
team int Matches involving this team id
status string notstarted, inprogress, finished, postponed, cancelled
date_from / date_to date YYYY-MM-DD. Without either, the list returns the next 7 days, oldest first. Any of the filters above lifts that window and returns the full archive, newest first.
limit / offset int Pagination
curl -H "Authorization: Token YOUR_API_KEY" \
  "https://sports.bzzoiro.com/csgo/api/v2/matches/?team=558&status=finished"
{"count": 110, "next": "...", "previous": null, "results": [
  {"id": 10843, "api_id": 451015,
   "tournament": {"id": 482, "name": "PGL Bucharest 2027: Closed Qualifier", "is_active": true},
   "home_team": {"id": 558, "name": "Team Nemesis", "short_name": "Team Nemesis",
                 "country_code": "", "country": ""},
   "away_team": {"id": 461, "name": "Nuclear TigeRES", "short_name": "Nuclear TigeRES",
                 "country_code": "", "country": ""},
   "start_time": "2026-10-16T08:55:00+00:00", "status": "finished",
   "home_score": 2, "away_score": 1, "best_of": 3, "stage": "",
   "home_elo_before": 1625, "away_elo_before": 1636}
]}
Field Meaning
home_score / away_score Maps won in the series
best_of Series format. Raised to what the final score proves (a 2-1 is a Bo3). Our match feed does not carry the format, so 1 also means not known: a 1-0 is a Bo1 or an unfinished Bo3, and an upcoming match has nothing to go on. Read best_of: 1 as "Bo1 or unknown".
home_elo_before / away_elo_before Each side's ELO going into this match (1500 = average). For a played match it is the rating held before it and does not change afterwards, so it is safe as a model feature. For an upcoming or live match it is the team's current rating, which does not include this match yet. null when not known — for example a match finished within the last few hours, before ratings are recalculated (every six hours).

GET /matches/live/ returns in-play matches only (cached 30 seconds). GET /matches/{id}/ returns the same fields plus maps.

maps is always an empty list. Per-map results (map names, round scores, halves, overtime) are not collected at the moment. The field stays in the response so code written against it keeps working; the series score is in home_score / away_score.

{"id": 10843, "status": "finished", "home_score": 2, "away_score": 1,
 "best_of": 3, "home_elo_before": 1625, "away_elo_before": 1636,
 "maps": []}

Odds#

GET /csgo/api/v2/matches/{id}/odds/ returns every price we hold for one match: the match-winner market flattened per bookmaker, and below it every other market the books offered, grouped by kind, line and period.

curl -H "Authorization: Token YOUR_API_KEY" \
  "https://sports.bzzoiro.com/csgo/api/v2/matches/8980/odds/"
{"match_id": 8980, "start_time": "2026-07-09T13:00:00+00:00",
 "home_team_name": "FaZe", "away_team_name": "MOUZ",
 "bookmakers_count": 16, "source": "multi",
 "bookmakers": [
   {"bookmaker": "Bet365", "bookmaker_slug": "bet365",
    "odds_home": 1.47, "movement_home": "STABLE",
    "odds_away": 2.59, "movement_away": "STABLE",
    "updated_at": "2026-07-09T10:04:43Z"}
 ],
 "markets": [
   {"market_kind": "OU", "market_family": "OU", "market_line": 2.5,
    "market_period": "FT", "selections": ["OVER", "UNDER"],
    "bookmakers": [
      {"bookmaker": "Bet365", "bookmaker_slug": "bet365",
       "prices": {"OVER": {"price": 2.02, "movement": "STABLE"},
                  "UNDER": {"price": 1.74, "movement": "STABLE"}},
       "updated_at": "2026-07-09T10:04:45Z"}
    ]}
 ]}
Field Meaning
source multi per-bookmaker prices, consensus the single stored price when the match is not yet linked to a priced feed event, none when nobody has priced it
bookmakers_count Bookmakers with at least one match-winner price
movement_* SHORTENING, DRIFTING or STABLE against the previous price we saw; null when there is no previous price
market_kind Market type: WINNER, AH (map handicap), OU (total maps or rounds)
market_family Distinguishes markets sharing a kind (total goals vs corners vs a single side's total)
market_line The handicap or total the market is quoted on; null for a plain winner market
market_period FT for the full match; period codes otherwise

Prices come from several sources and are pooled per match. A bookmaker appears once per market with its most recent price.

Predictions#

Query parameters

Param Type Description
upcoming bool Only matches not yet started
date_from / date_to date YYYY-MM-DD
limit / offset int Pagination

Without dates, only matches that have not started (upcoming defaults to true; pass upcoming=false for the archive). Probabilities are 0-1.

{"count": 17, "next": "...", "previous": null, "results": [
  {"id": 6494,
   "match": {"id": 10884, "start_time": "2026-10-18T17:00:00+00:00",
             "status": "notstarted", "best_of": 1,
             "home_team": {"id": 1048, "name": "6yuBoJlbl"},
             "away_team": {"id": 446, "name": "HyperSpirit"},
             "home_elo_before": 1500, "away_elo_before": 1413},
   "home_win_prob": 0.729, "away_win_prob": 0.271,
   "predicted_winner": "6yuBoJlbl", "confidence": 0.729,
   "home_form_score": 50.0, "away_form_score": 33.3,
   "h2h_advantage": 0.0, "is_correct": null}
]}

match is the full match object from the Matches section, shortened here.

Match stats#

GET /matches/{id}/stats/ returns how both sides were playing going into the match: round win rates split by side, pistol and opening-duel conversion, how each team picks and bans maps, and the head-to-head.

Every figure is an aggregate over the trailing three months, taken as one snapshot per match. collected_at says when that snapshot was taken, and pre_match whether that was before the scheduled start:

  • pre_match: true — taken before the start, so it cannot contain this match. Snapshots are only taken before the start and are never rewritten.
  • pre_match: false — an older snapshot taken after the start. Its window may include this very match, so do not use it as a pre-match feature. These are kept for reference rather than deleted.
  • null — nothing was collected for this match.
curl -H "Authorization: Token YOUR_API_KEY" \
  "https://sports.bzzoiro.com/csgo/api/v2/matches/10843/stats/"
{"match_id": 10843,
 "collected_at": "2026-10-15T14:17:10.130509+00:00", "pre_match": true,
 "teams": [
   {"team": {"id": 558, "name": "Team Nemesis", "short_name": "Team Nemesis",
             "country_code": "", "country": ""},
    "map_winrate": 66.13, "map_sample_size": 62,
    "round_winrate_all": 53.92, "round_winrate_t": 50.54, "round_winrate_ct": 57.25,
    "pistol_winrate": 49.19, "headshot_rate": 50.47, "opening_conversion": 72.44,
    "elimination_winrate": 34.12, "objective_winrate": 19.8,
    "first_to_rounds": {"3": 54.84, "6": 61.29, "9": 66.13, "12": 66.13},
    "avg_kills": 3.44, "avg_deaths": 3.18, "avg_assists": 1.39, "kd_ratio": 1.08,
    "avg_damage": 375.71, "avg_opening_kills": 0.55, "avg_awp_kills": 0.38,
    "avg_utility_damage": 31.53, "avg_flash_assists": 0.2,
    "form_results": [
      {"win": false, "date": "2026-10-08T12:08:19Z", "home_team": "Team Nemesis",
       "away_team": "Sinners Esports", "home_score": 1, "away_score": 2,
       "tournament": "Private Club #2: Closed Qualifier"}
    ]}
 ],
 "map_pool": [
   {"team": {"id": 558, "name": "Team Nemesis", "short_name": "Team Nemesis",
             "country_code": "", "country": ""},
    "maps": [{"map_name": "Inferno", "winrate": 24, "type": "picked"},
             {"map_name": "Dust2", "winrate": 28, "type": "picked"}]}
 ],
 "head_to_head": {"total_matches": 2, "home_wins": 1, "away_wins": 1,
   "recent_matches": [
     {"date": "2026-10-02T13:45:00Z", "home_team": "Team Nemesis",
      "away_team": "Nuclear TigeRES", "home_score": 2, "away_score": 1,
      "tournament": "CIS LAN Championship #8"},
     {"date": "2026-08-31T12:05:00Z", "home_team": "Team Nemesis",
      "away_team": "Nuclear TigeRES", "home_score": 1, "away_score": 2,
      "tournament": "Moscow Cyber Games 2026: European Qualifier"}
   ]}}
Field Meaning
*_winrate, headshot_rate, opening_conversion, first_to_rounds Percentages (0-100)
avg_*, kd_ratio Averages over the window
form_results Recent results, most recent first; win is from this team's point of view
map_pool[].maps[].winrate Despite its name, the share of the team's picks (type: picked) or bans (type: banned) that went to that map, in % — not a win rate. Picked shares add up to about 100.
head_to_head.home_wins / away_wins Wins of this match's home and away team in the meetings listed
head_to_head.recent_matches The last meetings, each with the home/away it was actually played with

GET /matches/{id}/player-stats/ returns the same idea per player — kills, deaths, damage, opening kills, utility and clutch rounds — each with the percentile that player sits in, so a number can be read without knowing what good looks like:

{"match_id": 10843,
 "collected_at": "2026-10-15T14:17:10.207750+00:00", "pre_match": true,
 "players": [
  {"nickname": "tronic", "real_name": "Kamil Shakhov",
   "team": {"id": 461, "name": "Nuclear TigeRES", "short_name": "Nuclear TigeRES",
            "country_code": "", "country": ""},
   "avg_kills": 0.68, "avg_deaths": 0.59, "avg_assists": 0.25,
   "kd_ratio": 1.16, "kd_percentile": 67.09,
   "avg_damage": 69.97, "damage_percentile": 42.01,
   "headshot_rate": 38.3, "headshot_percentile": 19.75,
   "avg_opening_kills": 0.11, "opening_kills_percentile": 71.27,
   "avg_awp_kills": 0.31, "avg_flash_assists": 0.05,
   "avg_utility_damage": 3.73, "avg_enemies_flashed": 0.53,
   "avg_multi_kill_rounds": 0.16, "multi_kill_percentile": 53.86,
   "avg_clutch_rounds": 0.01, "clutch_percentile": 88.39,
   "favourite_weapons": [{"weapon": "AWP", "avg_kills": 0.4}]}
]}

Values are per round over the same trailing three-month window, and collected_at / pre_match work exactly as on /stats/.

Both return empty lists (with collected_at and pre_match null) for matches with no stats collected — mostly lower-tier fixtures and matches that were already under way when we first saw them.

Coverage and off-season#

Tournaments run all year but not every week has one, and the schedule is usually published only days ahead.

GET /api/v2/coverage/?sport=csgo says so directly, without a token:

{"sport": "csgo", "status": "in_season", "events_next_7d": 6,
 "events_next_30d": 6, "priced_next_7d": 0, "live_now": 0,
 "next_event_at": "2026-08-20T08:00:00+00:00", "last_event_at": "2026-08-19T16:00:00+00:00"}

status is in_season (fixtures within seven days), between_events (nothing this week, something later), off_season (nothing ahead and the last event is more than a fortnight old) or no_fixtures. Drop the ?sport= to get every sport in one call — worth doing before a polling loop decides what to ask for.

View this page as Markdown · Found a mistake? Tell us