VLRdevAPI

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,
) -> ReplayData

With curried access:

vlrdevapi.series(series_id).replay(game_id: int) -> ReplayData

Parameters

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

On this page