Series Replay
Fetch the kill-feed replay timeline per round for a game - kills with weapons and positions, spike plants/defuses, resurrections, and alive-player snapshots with facing angles.
series.replay(series_id, game_id) returns the kill-feed replay timeline for each round of a specific game within a series (the logs tab on vlr.gg).
Signature
series.replay(
series_id: int,
game_id: int,
) -> ReplayDataWith curried access:
vlrdevapi.series(series_id).replay(game_id: int) -> ReplayDataParameters
Prop
Type
Returns - ReplayData
Prop
Type
round(round_number) helper
ReplayData.round(n) returns the ReplayRound for one 1-based round number, or None when that round has no replay data.
∇ ReplayRound
Prop
Type
∇ ReplayEvent
One event in the round timeline. type is a ReplayEventType enum (KILL, PLANT, DEFUSE, BOOM, or RESURRECT; .value gives the lowercase string and .full_name a human-readable label); derived: True marks the two kinds vlr.gg does not log as feed rows (the spike detonation and resurrections).
Prop
Type
∇ ReplayActor / ReplayAlivePlayer
Prop
Type
∇ ReplayPlayerRef
Prop
Type
Examples
Round 1 kill feed
Walk the first round's timeline with killer, cause, and positions.
import vlrdevapi
replay = vlrdevapi.series.replay(series_id=754737, game_id=283288)
print(f"{replay.team1} ({replay.team1_id}) vs {replay.team2} ({replay.team2_id})")
print(f"{replay.num_rounds} rounds")
for ev in replay.round(1).events:
who = ev.actor.name if ev.actor else "-"
print(f"{ev.seq}. {ev.seconds}s {ev.type.full_name}: {who}", end="")
if ev.cause:
print(f" ({ev.cause})", end="")
if ev.victim:
print(f" -> {ev.victim.name}", end="")
print()Killer-to-victim distance
For every kill, distance_units is the Euclidean distance between the killer (from_x/from_y) and the victim's death spot (pos_x/pos_y) in minimap units. There is no meters scale on vlr.gg, so treat the values as relative (consistency across fights of the same map is what matters).
import vlrdevapi
from vlrdevapi._series.replay.models import ReplayEventType
replay = vlrdevapi.series.replay(series_id=754737, game_id=283288)
long_range = [
ev
for rd in replay.rounds
for ev in rd.events
if ev.type is ReplayEventType.KILL and ev.distance_units is not None
]
long_range.sort(key=lambda ev: ev.distance_units, reverse=True)
for ev in long_range[:5]:
print(f"{ev.clock} {ev.actor.name} -> {ev.victim.name}: {ev.distance_units:.0f} units ({ev.cause})")Spike plants and detonation
Every spike event carries the site letter and the minimap position. Spike detonations are vlr.gg round markers rather than kill-feed rows, so they surface as a derived boom event (derived: True).
import vlrdevapi
from vlrdevapi._series.replay.models import ReplayEventType
replay = vlrdevapi.series.replay(series_id=754737, game_id=283288)
for ev in replay.round(14).events:
if ev.type in (ReplayEventType.PLANT, ReplayEventType.DEFUSE, ReplayEventType.BOOM):
print(f"{ev.clock or '-'} {ev.type.full_name} site {ev.site} at ({ev.pos_x}, {ev.pos_y})")Resurrections
Sage and Clove resurrections do not appear as feed rows on vlr.gg - a revived player simply shows up alive again in a later event snapshot. vlrdevapi derives them as resurrect events whose actor is the revived player (the caster cannot be identified from the data).
import vlrdevapi
from vlrdevapi._series.replay.models import ReplayEventType
replay = vlrdevapi.series.replay(series_id=378829, game_id=180437)
for rd in replay.rounds:
for ev in rd.events:
if ev.type is ReplayEventType.RESURRECT:
print(f"R{rd.round_number} {ev.clock}: {ev.actor.name} (ID {ev.actor.player_id}) back to life")Alive players and facing angles
Each event snapshots every living player's minimap position and the direction they were looking. Angles are in minimap degrees (0 points to +x, rotating clockwise); coordinates use the same 1024x1024 grid vlr.gg renders its minimaps on, so they map straight onto the map image with no conversion factor.
import vlrdevapi
replay = vlrdevapi.series.replay(series_id=754737, game_id=283288)
first_blood = replay.round(9).events[0]
for p in first_blood.alive:
print(f"{p.name:<10} {p.agent:<8} {p.team:<15} at ({p.x:.0f}, {p.y:.0f}) facing {p.view_angle}deg")Notes
- Player and team IDs are real vlr.gg IDs, resolved from the game's overview tab (one extra request). Players are matched by name strictly within their own team; a name is used as a fallback only when it maps to exactly one ID across the match, so duplicate names can never take another player's ID - unmatched players keep 0.
- If a map is unplayed or vlr.gg carries no round data for it, the result comes back empty (
rounds == [],num_rounds == 0) with team/player IDs left at 0 - no errors, and no enrichment requests are made. - Coordinates, angles, and distances are all raw minimap units; vlr.gg renders markers directly on a fixed 1024x1024 minimap image with no per-map conversion factor.
- Ultimate and ability kills are logged as the kill
cause(e.g.Overdrive,Tour de Force,Not Dead Yet); the cast itself is not an event.
Last updated on