The VCT Dataset
Every page on this site is a view over one file — and this is the file.
vct.duckdb A permanent URL that always resolves to the current build, so it is safe to cite or script against.
A single DuckDB database containing every tier 1 VCT match since 2021 — all international events (Masters, Champions) plus the regional events that qualify into them. Series, maps, rounds, per-player map stats, kill matchups and clutch/multikill events, refreshed daily. Free to use, including commercially, with no warranty; because the data is derived from publicly available professional match pages, the Terms of Service are the statement of what you're agreeing to rather than an off-the-shelf licence identifier. A link back to vct-reference.com is appreciated.
One caveat before you trust a number: coverage is uneven —
China has no economy data at all and only ~8% of its maps carry the
multikill/clutch and kill-matrix blocks, and side splits are missing on some
maps everywhere. Gate on maps.performance_available /
economy_available and divide side stats by side-gated
denominators, or the gaps read as zeros. Full figures on
Methodology.
Tables
matches- One row per series — teams, event, stage, format, result, true-UTC timestamp.
maps- One row per map played, with the round score and which optional stat blocks were available.
rounds- One row per round — winner, side, how it ended, and the economy snapshot where recorded.
player_map- One row per player per map — rating, ACS, ADR, KAST, HS%, K/D/A, opening duels, multikills and clutches, each split overall / attack / defense.
kill_matrix- One row per player pair per map — who killed whom, including Operator and opening kills.
notables- One row per multikill or clutch event, with the round and the opponents involved.
players- One row per player — stable id, handle, country.
teams- One row per team — stable id, name, country.
Examples
Both run as-is in the DuckDB CLI (duckdb vct.duckdb) or the
Python package.
Career kills leaderboard
SELECT p.player_name, SUM(pm.kills_all)::INT AS kills
FROM player_map pm
JOIN players p USING (player_id)
JOIN matches m USING (match_id)
WHERE m.listing_status = 'Completed' AND m.is_showmatch = FALSE
GROUP BY 1 ORDER BY kills DESC LIMIT 25; Clutches per map, coverage-gated
SELECT p.player_name,
ROUND(AVG(pm.clutch_1v1 + pm.clutch_1v2 + pm.clutch_1v3
+ pm.clutch_1v4 + pm.clutch_1v5), 3) AS per_map
FROM player_map pm
JOIN players p USING (player_id)
JOIN maps mp USING (match_id, game_id)
WHERE mp.performance_available -- without this, unscraped maps count as zeros
GROUP BY 1 HAVING COUNT(*) >= 100
ORDER BY per_map DESC LIMIT 25;