Programmatic match data and hero leaderboards (official and community APIs)
Question
How do you programmatically obtain (a) match history and per-match stats for a specific player, and (b) "top 100 players per hero" leaderboards for Deadlock — and is any of it an official Valve API?
Summary
Everything network-side below was exercised with real HTTP calls on 2026-08-16, against game build 6679 (which the official IGCVersion_1422450 endpoint confirmed as the live version the same day). Claims taken from external sources rather than calls are labelled reported.
| want | use | status | ||||
|---|---|---|---|---|---|---|
| An official Valve Web API for match data | Does not exist. Only IGCVersion_1422450 (GC version numbers) is exposed for app 1422450 on api.steampowered.com | verified (keyless interface list; keyed list unchecked) | ||||
| Player match history (per-match stats) | GET https://api.deadlock-api.com/v1/players/{account_id}/match-history | verified by call — 5,323 matches, no key needed | ||||
| Full parsed data for one match | GET https://api.deadlock-api.com/v1/matches/{match_id}/metadata | verified by call — ~1 MB JSON, 12 players, items/abilities/damage | ||||
| Raw match metadata straight from Valve | GET .../v1/matches/{match_id}/salts → http://replay{cluster}.valve.net/1422450/{match_id}_{metadata_salt}.meta.bz2 | verified by call (HEAD 200 on the Valve replay server) | ||||
| Region leaderboard (top ~1000) | `GET .../v1/leaderboard/{Europe\ | Asia\ | NAmerica\ | SAmerica\ | Oceania}` | verified by call — 1000 entries, names only, no account ids |
| "Top 100 per hero" from Valve's board | Not available. GET .../v1/leaderboard/{region}/{hero_id} returns the few board players whose top heroes include that hero (9 for Infernus in Europe) | verified by call | ||||
| A real top-N-per-hero ranking | GET .../v1/analytics/scoreboards/players?sort_by=...&hero_id=... (deadlock-api's own DB, not a Valve ladder) | verified by call | ||||
| Doing it yourself at the protobuf level | Steam GC via a logged-in bot (SteamKit2 / node-steam-user); messages in SteamDatabase/Protobufs deadlock/ | messages verified in repo; running a bot not attempted |
Scope: this note answers the match-history and leaderboard question. Note 0011 maps the complete API surface — every host in the deadlock-api.com family, all 31 endpoint groups of its OpenAPI spec, licenses, and the rest of the ecosystem — and feeds the site's rendered reference at /api/.
Biggest caveats: the community API is the de-facto standard but explicitly not endorsed by Valve; Valve throttled trackers hard in September 2024 (reported); fresh-from-Steam match history requires the target account to friend one of deadlock-api's bots; and the "player" identifier everywhere is the 32-bit account_id (SteamID3), not SteamID64.
Findings
1. There is no official Deadlock Web API
The keyless Steam Web API interface list contains exactly one interface for app 1422450:
$ curl -s "https://api.steampowered.com/ISteamWebAPIUtil/GetSupportedAPIList/v1/"
27 interfaces (keyless view)
citadel/deadlock hits: ['IGCVersion_1422450']
IGCVersion_1422450 serves game-coordinator version numbers and nothing else — and it agrees exactly with the local install (tools/find_game.py → ClientVersion 6679):
$ curl -s "https://api.steampowered.com/IGCVersion_1422450/GetClientVersion/v1/"
{ "result": { "success": true, "min_allowed_version": 6679, "active_version": 6679 } } HTTP 200
There is no Deadlock equivalent of Dota 2's IDOTA2Match_570. Caveat: the keyless list omits interfaces that require an API key; checking the keyed list needs a registered Steam Web API key (UNVERIFIED — see Open Questions).
Valve's stance. No published rules or primary-source developer statement about third-party API usage were found. What is on the record (reported, secondary sources): around 2024-09-02 Valve applied aggressive rate limits that stopped trackers (tracklock.gg's creator: "Valve turned on even more aggressive rate limits and now there are no new games being tracked" — Dexerto, 2024-09-05); by mid-October 2024 tracking worked again (Inven Global). The current posture is tolerated-but-unendorsed; deadlock-api.com itself carries the disclaimer "deadlock-api.com is not endorsed by Valve…" (verified in its openapi.json info block). Build accordingly: this can be turned off again at any time.
2. The Game Coordinator route (how the data actually flows)
All match data ultimately comes from the Steam Game Coordinator over protobuf, exactly like Dota 2. The messages are tracked in SteamDatabase/Protobufs under deadlock/ (48 files). Verified by downloading citadel_gcmessages_client.proto (55,923 bytes) and reading the definitions:
| message | purpose | key fields (verified) |
|---|---|---|
CMsgClientToGCGetMatchHistory | per-player history | account_id (uint32), continue_cursor for paging |
CMsgClientToGCGetMatchHistoryResponse | matches[] with match_id, hero_id, kills/deaths/assists, net_worth, ranked_display_badge…; EResult includes k_eResult_InvalidPermission = 2 and k_eResult_RateLimited = 5 | |
CMsgClientToGCGetMatchMetaData | salts for one match | match_id, returns metadata_salt, replay_salt |
CMsgClientToGCGetLeaderboard | region board | leaderboard_region, optional hero_id — hero filtering is native to the GC |
CMsgClientToGCGetLeaderboardResponse | entries carry only account_name, rank, top_hero_ids — no account id | |
CMsgClientToGCGetAccountLeaderboards | one account's board placements | account_id → (region, hero_id, rank) entries |
ECitadelLeaderboardRegion (in citadel_gcmessages_common.proto): Europe, Asia, NAmerica, SAmerica, Oceania.
With metadata_salt in hand, the match metadata is a plain unauthenticated HTTP GET from Valve's replay infrastructure — pattern http://replay{cluster_id}.valve.net/1422450/{match_id}_{metadata_salt}.meta.bz2 (bz2-compressed CMsgMatchMetaDataContents protobuf). Verified live:
$ curl -sI "http://replay273.valve.net/1422450/99570153_30254059.meta.bz2"
HTTP/1.1 200 OK
content-type: application/octet-stream
last-modified: Sat, 15 Aug 2026 11:36:28 GMT
Talking to the GC yourself requires a logged-in Steam account session (SteamKit2, node-steam-user + the app's GC channel) — this note did not run a bot; the message shapes above are what a bot would exchange. deadlock-api.com operates such bots and also crowd-sources salts via deadlock-api-ingest, which scans contributors' local Steam HTTP caches for replay URLs (reported).
3. deadlock-api.com — the de-facto community API
Alive and free as of 2026-08-16. Base https://api.deadlock-api.com; interactive docs at /docs, spec at /openapi.json (116 paths; title "Deadlock API", version 0.1.0). The former separate assets host now redirects into it (assets.deadlock-api.com/v2/heroes → 301 → /v1/assets/heroes, verified). Open source: deadlock-api/deadlock-api. No key needed for anything tested below; an optional key (X-API-KEY header or api_key query, per the spec's security schemes) raises the per-endpoint limits and is obtained via their Discord (reported).
Player match history — the core answer to question (a):
$ curl -s "https://api.deadlock-api.com/v1/players/112724001/match-history"
HTTP 200, 3,256,579 bytes, 5,323 matches. Newest entry (abridged):
{ "match_id": 99570153, "hero_id": 60, "player_kills": 4, "player_deaths": 3,
"player_assists": 21, "match_result": 0, "start_time": 1786791854,
"match_duration_s": 1770, "net_worth": 32467 }
(Account 112724001 is the Europe leaderboard's rank-1 player — an obviously public example. Fields mirror CMsgClientToGCGetMatchHistoryResponse.Match, plus ranked badge fields.) Per the endpoint's own docs: if the account is friends with one of their Steam bots the response merges a fresh GC fetch with their ClickHouse store; otherwise you get only what ClickHouse already ingested. Rate limits (documented in the spec): 100 req/s per IP for cached reads; bot-friend refreshes 10 req/h per IP (300 req/h with key); force_refetch=true capped at 1 req/h.
Per-match stats:
$ curl -s "https://api.deadlock-api.com/v1/matches/99570153/metadata"
HTTP 200, 1,018,164 bytes. Keys: banned_hero_ids, hero_build_ids, match_info.
match_info.players: 12 entries, each with ability_stats, items, death_details,
assigned_lane, hero_id, kills/deaths/assists, ...
Sourcing tiers with different limits: cache (100 req/s) → their S3 (100 req/10s keyless) → live Steam fetch (3 req/h keyless, 300 req/h with key).
$ curl -s "https://api.deadlock-api.com/v1/matches/99570153/salts"
{"match_id":99570153,"cluster_id":273,"metadata_salt":30254059,"replay_salt":null,
"metadata_url":"http://replay273.valve.net/1422450/99570153_30254059.meta.bz2","demo_url":null}
Scale (from GET /v1/info, verified): ~61,700 matches fetched/day, 472 M rows of per-player match history, 23.9 M stored match salts.
Finding players. GET /v1/players/steam-search?search_query=... (name search) and GET /v1/players/steam?account_ids=... (profile by id) both worked keyless. The identifier is the 32-bit account_id; conversion verified against Steam itself: 76561198072989729 (steamID64) − 76561197960265728 = 112724001, and steamcommunity.com/id/cantsaymeow/?xml=1 returns that steamID64 with the same persona name the API reported.
4. Leaderboards — and why "top 100 per hero" is not what you think
GET /v1/leaderboard/{region} returns Valve's board (updated hourly per the docs — the GC message relayed verbatim; /raw serves the undecoded CMsgClientToGCGetLeaderboardResponse).
$ curl -s "https://api.deadlock-api.com/v1/leaderboard/Europe" # 1000 entries
{ "account_name": "hate being sober",
"possible_account_ids": [112724001, 1557830260, 1645809056],
"rank": 1, "top_hero_ids": [] }
Observations (all from calls on 2026-08-16):
- The full region board has 1000 entries. Entries carry a display name, a rank, and
top_hero_ids— the protobuf has no account-id field, so identity is not part of Valve's leaderboard.possible_account_idsis deadlock-api's own name-matching heuristic and is frequently ambiguous (one entry listed 20+ candidates). - The hero-filtered board
GET /v1/leaderboard/Europe/{hero_id}is small: hero 1 (Infernus) → 9 entries, hero 7 → 37, hero 15 → 57, hero 25 → 5, each re-numbered from rank 1. The/rawvariant for hero 1 was 105 bytes — this is Valve's answer, not a lossy view. "Top 100 per hero" does not exist in Valve's data; the hero filter yields however many qualifying players there are (semantics not fully pinned down — see Open Questions). - Region path values are
Europe,Asia,NAmerica,SAmerica,Oceania(capitalised; a wrong value 400s with the valid list in the body).
For an actual "top N players on hero X", the workable route is deadlock-api's analytics over its own match database:
$ curl -s "https://api.deadlock-api.com/v1/analytics/scoreboards/players?sort_by=wins&hero_id=1&min_matches=20&min_unix_timestamp=1752600000&limit=3"
[{"rank":0,"account_id":127308683,"value":916.0,"matches":1626},
{"rank":1,"account_id":270463672,"value":656.0,"matches":1282},
{"rank":2,"account_id":46310648,"value":560.0,"matches":1023}]
sort_by supports matches, wins, winrate, kill/death/damage aggregates, etc.; filters include min_average_badge, time windows, and limit (so top-100 is one call). This ranks by observed performance in their DB — it is not a Valve MMR ladder. /v1/players/mmr/{hero_id} exists but its own docs deprecate it: "Valve reports a single account-wide rank, not a per-hero one." Analytics endpoints share a pool of 200 req/min per IP.
5. Privacy gating
- The GC anticipates permission-gated history:
k_eResult_InvalidPermissioninCMsgClientToGCGetMatchHistoryResponse(verified in the proto), and the client build 6679 localizes it —citadel_main_english.txt:"Citadel_MatchHistory_InvalidPermission" "You do not have permission to view this user's match history"(verified locally). - No Dota-style "Expose Public Match Data" opt-in was found in build 6679's client strings — grepping
citadel_main_english.txtfor privacy/visibility settings surfaces onlycitadel_settings_streamer_mode("hide player names in-game and on the dashboard"). Observation of absence, not proof there is no server-side flag. - Practically, per deadlock-api's endpoint docs: fresh history for an arbitrary account requires that account to friend one of their bots; without that you get their already ingested data — which, at ~62 k matches/day ingested, covered the full 5,323-match history of the example account.
- deadlock-api offers players a Steam-OpenID-verified opt-out that deletes stored data and blocks future requests (their
/data-privacypage, reported via fetch).
6. Other community layers (liveness only)
HTTP probes on 2026-08-16: statlocker.gg 200, tracklock.gg 200, deadlocktracker.gg 200, deadlocklabs.gg 403-to-curl (bot filter; site reported alive). Their data provenance was not audited; deadlocklabs states it uses "the public Deadlock API pipeline" (reported). For building anything, deadlock-api.com is the layer they sit on or parallel to.
Reproduce
python tools/find_game.py # ClientVersion 6679
# 1. Official surface
curl -s "https://api.steampowered.com/ISteamWebAPIUtil/GetSupportedAPIList/v1/" | grep -o '"IGCVersion_1422450"'
curl -s "https://api.steampowered.com/IGCVersion_1422450/GetClientVersion/v1/"
# 2. Protobuf layer
curl -sO "https://raw.githubusercontent.com/SteamDatabase/Protobufs/master/deadlock/citadel_gcmessages_client.proto"
grep -n -E "message CMsg.*(MatchHistory|MatchMeta|Leaderboard)" citadel_gcmessages_client.proto
# 3. Community API — leaderboards
curl -s "https://api.deadlock-api.com/v1/leaderboard/Europe" | python -c "import json,sys;print(len(json.load(sys.stdin)['entries']))"
curl -s "https://api.deadlock-api.com/v1/leaderboard/Europe/1" | python -c "import json,sys;print(len(json.load(sys.stdin)['entries']))"
# 4. Community API — player + match (public example: Europe board rank 1)
curl -s "https://api.deadlock-api.com/v1/players/steam?account_ids=112724001"
curl -s "https://api.deadlock-api.com/v1/players/112724001/match-history" | head -c 400
curl -s "https://api.deadlock-api.com/v1/matches/99570153/salts"
curl -sI "http://replay273.valve.net/1422450/99570153_30254059.meta.bz2" | head -2
# 5. Top-N-per-hero (community ranking, not a Valve ladder)
curl -s "https://api.deadlock-api.com/v1/analytics/scoreboards/players?sort_by=wins&hero_id=1&min_matches=20&limit=3"
# 6. Local privacy-setting check (build 6679)
grep -i "MatchHistory_InvalidPermission\|streamer_mode" \
"C:/Program Files (x86)/Steam/steamapps/common/Deadlock/game/citadel/resource/localization/citadel_main/citadel_main_english.txt"
Match ids, salts, entry counts and the example account's stats will drift — they are live data. The shapes and status codes are the claims.
Gotchas
- Leaderboard region casing: the path wants
Europe/NAmerica/…, capitalised. Lower-case 400s. The spec'sRegionModeschema (row,europe, …) is a different enum used by analytics filters — do not read it as the leaderboard's. - Hero-filtered leaderboard ranks are renumbered 1..N within the filter, and N is usually single-to-double digits. Anything presenting it as "top 100 per hero" is synthesizing data Valve does not provide.
- Leaderboard identity is a display name.
possible_account_idsis a heuristic; never treat it as a resolved identity without confirming (e.g. via match history). account_idmeans SteamID3 (32-bit) everywhere: SteamID64 − 76561197960265728.openapi.jsonis UTF-8; on Windows,open()withoutencoding='utf-8'dies in cp1252. The match-history response is 3 MB+ — don't fetch it in a loop.assets.deadlock-api.comanswers 301 — follow redirects (curl -L) or useapi.deadlock-api.com/v1/assets/...directly.- Steam-sourced fetches are severely limited keyless (3 req/h metadata, 10 req/30 min salts per IP). Cached reads are generous (100 req/s). Design for the cache tier.
- The whole community layer is unofficial. It broke for weeks in September 2024 when Valve tightened rate limits; assume it can break again without notice.
Open questions
- Keyed interface list unchecked:
GetSupportedAPIListwith a registered Steam Web API key may reveal more 1422450 interfaces (Dota's match interfaces are key-gated). Needs a key from <https://steamcommunity.com/dev/apikey>. - Hero-board semantics not pinned: Europe's full board showed only 4 entries with hero 1 in
top_hero_ids, yet the hero-1 board had 9 — so it is not simply a filter of the returned top-1000. Whether it draws from a deeper ladder, and what qualifies a player for it, is unknown. - No primary-source Valve statement found on third-party API policy — only secondary reporting of the September 2024 throttling. If someone finds the actual Discord post, link it here.
- GC-honored scope of
GetMatchHistory(account_id)was not tested with a live bot: whether the GC today answers for arbitrary non-friend accounts, and when it returnsInvalidPermission, is inferred only from deadlock-api's docs and the proto. - Not tested: the demo/replay
.dem.bz2download path (only metadata was HEAD-checked), the live-events API, custom-match endpoints, database dumps, and the provenance of statlocker/tracklock/deadlocktracker. - All rate-limit figures are the spec's self-documentation, not empirically probed.
Sources
- deadlock-api OpenAPI spec — fetched and parsed 2026-08-16 (verified).
- deadlock-api/deadlock-api, deadlock-api-ingest — provenance claims reported.
- SteamDatabase/Protobufs,
deadlock/— proto files fetched and read (verified). - Dexerto, 2024-09-05: Valve's shutdown of Deadlock stat trackers — reported.
- Inven Global: How to track your MMR in Deadlock — reported.
- Steam discussion "Developer API" (app 1422450) — community demand, no Valve reply; reported.
- deadlock-api.com/data-privacy — opt-out flow; reported via fetch.