CS2 data reference

Player depth

Weapons, grenades, hitgroups, duels and flashes — the deepest layer, and the least universal.

GET /v1/{game}/matches/{id}/depth returns a single object containing five arrays. Each is [] when unavailable, never null.

This is the least universally available layer — check data_available.depth on the match, or filter the match list with ?has=depth, before requesting it.

weapons

One row per player per weapon per side they held it on, so a player who used an AK-47 on both sides appears twice.

weapon_slug (ak-47) is the stable key to group on; weapon_name (AK-47) is for display; weapon_class buckets it (Rifle, Pistols, SMG, Heavy, Grenade, Equipment, or an empty string where the source didn't classify it).

Counters: shots, hits, kills, headshots, damage, wall_bangs, trade_kills. hits/shots is an accuracy rate — guard the zero denominator, since a weapon can be equipped without being fired.

grenades

One row per player per grenade type per side: throws, hits, kills, owns. grenade_name is one of Flashbang, Smoke Grenade, HE Grenade, Molotov, Incendiary Grenade or Decoy Grenade.

hitgroups

Where shots landed: hit_group — one of Head, Chest, Stomach, LeftArm, RightArm, LeftLeg, RightLeg, Gear or Generic — with hits, damage and kills whose finishing shot landed there.

duels

One row per ordered (killer, victim) pair, match-level. A mutual rivalry is two rows — swap the ids to find the reverse direction.

kills is how many times the killer killed the victim; weapon_names is a comma-separated string in kill order.

kind says what the pair is, and the vocabulary is closed:

kindMeaning
enemyAn ordinary duel against the opposition.
teamA kill on a teammate.
selfA self-inflicted death — world damage, own grenade, a fall. killer_player_id and victim_player_id are the same player.

Filter on kind before you total anything. kills is a magnitude for all three kinds, so summing a player's rows without filtering to enemy counts their team kills and their own deaths as kills on the opposition.

depth = get(f"/v1/cs2/matches/{match_id}/depth")["data"]

kills_on_enemies = sum(d["kills"] for d in depth["duels"] if d["kind"] == "enemy")
team_kills       = sum(d["kills"] for d in depth["duels"] if d["kind"] == "team")

Kill counts elsewhere are net of team kills

This matrix is the only place a team kill is separable. Everywhere else a kill count is the net figure: a team kill subtracts one. That holds for kills on match stats and for kills on the weapon rows above, which is why a weapon row can carry a negative kills — a player whose only molotov kill was a teammate has kills: -1 for molotov. Around 3,400 weapon rows across the corpus are negative this way, most of them grenades.

Don't clamp those to zero. The negative is what keeps a player's weapon rows summing to their match kills, and the duel matrix is where you go to recover the team-kill count itself. damage nets the same way, for the same reason — about 630 weapon rows are negative on damage.

Two consequences worth coding around:

  • headshots does not follow kills's netting one-for-one, so a weapon row can report more headshots than kills — 5,948 rows do, 741 of them with a negative kills. A per-row headshots / kills is not a rate. Sum a player's weapon rows first, or take the headshot percentage from match stats.
  • Don't filter a weapon list on kills > 0. Only 1.97M of the corpus's 5.05M weapon rows have a positive kills; 3.08M sit at exactly zero and 3,428 are negative. Those are still weapons the player used — all but 15 rows in the whole corpus record at least one shot, and 2,685 of the negative rows carry positive damage. For "weapons this player used", filter on shots > 0 OR damage <> 0 OR kills <> 0.

flashes

One row per ordered (flasher, flashed) pair. Self-flashes are real rows where flasher_player_id == flashed_player_id — they are recorded, not filtered, and dropping them is a decision you should make deliberately.

count is how many times; duration_ns is total blind time in nanoseconds — divide by 1e9 for seconds. It's an int64 because a match total exceeds 32-bit range.

count counts blinds on an opponent only. Blinding a teammate is recorded with count: 0 and a real duration_ns, so a row with no count is not an empty row. Corpus-wide, 99.8% of same-team rows carry count: 0 against 0.05% of opposing rows, and every zero-count row has a non-zero duration. Use duration_ns when you want blind time inflicted on anyone, and count when you want blinds that cost the opposition.

depth = get(f"/v1/cs2/matches/{match_id}/depth")["data"]

for f in depth["flashes"]:
    if f["flasher_player_id"] == f["flashed_player_id"]:
        continue  # self-flash
    print(f"{f['flasher_player_id']} blinded {f['flashed_player_id']} "
          f"{f['count']}× for {f['duration_ns'] / 1e9:.1f}s")

Everything here is match-level

None of the five arrays carry map_number — the upstream exposes them per match, not per map. If you need per-map weapon usage, that isn't available.

On this page