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:
kind | Meaning |
|---|---|
enemy | An ordinary duel against the opposition. |
team | A kill on a teammate. |
self | A 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:
headshotsdoes not followkills's netting one-for-one, so a weapon row can report more headshots than kills — 5,948 rows do, 741 of them with a negativekills. A per-rowheadshots / killsis 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 positivekills; 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 positivedamage. For "weapons this player used", filter onshots > 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.