CS2 data reference

Broadcast streams

Where a match in progress is being shown — channel, language and embed URL. A live pointer, not a history.

GET /v1/{game}/matches/{id}/streams returns the channels broadcasting a match right now. The same rows embed via include=streams on GET /v1/{game}/matches/{id} and on the bulk endpoint — which is how you ask "what is on air?" in one request instead of one per match.

This is a live pointer, not a record. A match's whole channel list is replaced on every refresh, and it empties when the match ends. An empty data: [] is the normal, correct answer for every match that is not currently being played — it does not mean the match was never streamed.

The shape

FieldMeaning
idThe row's id (UUIDv7). It changes when the set is replaced — don't store it as a stable key for a channel.
match_idThe match being broadcast.
platformtwitch, kick, youtube or other, derived from the URL's host.
channelThe channel's display name (falls back to the last path segment of the URL).
urlThe page a viewer would open. Unique per match.
embed_urlA player URL suitable for an <iframe>, when one is published. Absent otherwise.
languageLowercase ISO 639-1 where stated (en, pt, ru, …). Absent otherwise.
officialWhether this is the organiser's own broadcast rather than a co-stream.
viewersConcurrent viewers as last observed, or null when not reported. Always a point-in-time number — see below.
observed_atWhen we last saw this channel on this match (RFC 3339, UTC).

Rows come back official first, then by viewers descending (null last), then by url — so data[0] is the broadcast most people are watching.

Freshness

An in-progress match's channel list is re-read on each collection pass — in practice every few minutes. viewers carries the same freshness, so read it as "roughly this many, a few minutes ago": it is useful for ranking co-streams against each other, not as a counter. observed_at is the honest timestamp for both, and it is per row.

Channels are picked up once a match is in progress, not while it is still scheduled. A match whose start time has passed but that has not gone live yet will usually have an empty list.

Getting the live slate in one request

include= is accepted on the match detail route and on /matches/bulk — not on the plain /matches list. Bulk takes the same filters, so one call gets the whole live slate with its channels:

live = get("/v1/cs2/matches/bulk", params={"status": "live", "include": "streams"})["data"]

for m in live:
    if not m["streams"]:
        continue
    top = m["streams"][0]
    print(f'{m["team_a_name"]} vs {m["team_b_name"]}: {top["channel"]} ({top.get("language", "?")})')

include=streams is present-but-empty when you ask for it and the match has none, and absent when you don't — so "streams" in m tells you whether you asked, and m["streams"] == [] tells you nothing is on air.

What it is not

  • Not a VOD or highlight archive. Nothing is kept after the broadcast ends. If you need the history of who streamed what, snapshot it yourself while the match is live.
  • Not in data_available or ?has=. Every flag there is monotonic — once a match has stats it always has stats — and streams are not: they appear and then go away. Filter live matches with ?status=live and read the list.
  • Not an endorsement, and not a paid placement. We receive nothing for listing them; the URLs are the public channel pages the tournament itself points at.

On this page