A single match
One match by id, with the same fields as a list row minus the denormalised names — a detail row carries bare team_a_id/team_b_id/tournament_id, so resolve those separately or read them off the list…
One match by id, with the same fields as a list row minus the denormalised names — a detail row carries bare team_a_id/team_b_id/tournament_id, so resolve those separately or read them off the list route instead. The data_available flags tell you which sub-resources (maps, stats, map_stats, vetoes, rounds, depth, lineups, odds) actually exist for this match, so you can skip requests that would return an empty list. Pass include= to embed related resources or the sub-resources themselves in this one response.
The primary way to authenticate: Authorization: Bearer <api-key>. When a request also carries ?apiKey=, the header wins. We store only a SHA-256 hash of a key, never the key itself. To try the API with no account, use the public sample key eo_sample_public: one published key, shared by everyone, that reads a fixed sample of past matches within daily limits. For evaluation; our Terms apply.
In: header
Path Parameters
Game slug. cs2 is the only populated title today; an un-onboarded game 404s cleanly.
Resource identifier — either a UUID or the resource's human-readable slug, scoped to {game}. /v1/cs2/teams/natus-vincere and /v1/cs2/teams/019f23d1-fb5c-7987-b522-47c3a5e72111 address the same row, so you can go straight from a slug you already have without first looking up its id.
Slugs work on teams, players and tournaments. Matches have no slug, so a match id must be a UUID. Either way, an identifier that resolves to nothing returns 404 not_found.
A team or a player also answers to a slug it was renamed away from, as long as no current team or player holds that slug — so a link made before a rename keeps working. The row returned carries its current slug.
Query Parameters
Comma-separated related resources to embed inline: teams (adds team_a/team_b), tournament, and odds (the latest derived line per outcome). include=teams,tournament,odds turns the four requests a match view needs into one. Unknown names are ignored rather than rejected, so adding an expansion later never breaks an existing client. Omit it and the response is exactly as before — every embedded field is absent, not null.
The match's own data can be embedded the same way: maps, stats, vetoes, rounds, depth, lineups, streams and players, each identical to what the matching /matches/{id}/... route returns — except players, which has no standalone route: it is the union of the players named by this match's lineups and stats rows, resolved to full rows, so a lineup or a scoreboard can be rendered without one /players/{id} request per player. include=maps,stats,vetoes,rounds,depth,lineups,players replaces six follow-up requests with none. A requested one is always present (empty when the match has none); to do this for many matches at once use /v1/{game}/matches/bulk.
Only the detail route offers this: a match row from /v1/{game}/matches already carries team_a_name, team_b_name, team_a_short, team_b_short and tournament_name denormalised, whereas the detail route returns bare ids.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/cs2/matches/019f2858-e253-7f3d-bdff-bc7738bd1036"{ "data": { "id": "01a0a3e2-96a5-7a9f-8af2-fae0f1a7d678", "game_id": "019f23d1-fb5b-7d24-935d-e4d68f6f565a", "tournament_id": "01a09164-2d84-7c6e-9d80-a42c8c70ce12", "team_a_id": "019f2858-e255-78e4-9e7c-32f307dfac91", "team_b_id": "01a0a3e2-96a0-7bf7-986c-6a68e151de5c", "format": "bo3", "status": "completed", "stage": "Circuit X Curitiba 3 Group D", "scheduled_at": "2026-09-18T02:25:00Z", "started_at": "2026-09-17T19:56:50.826573Z", "ended_at": "2026-09-18T03:44:28Z", "score_a": 2, "score_b": 0, "winner_team_id": "019f2858-e255-78e4-9e7c-32f307dfac91", "result_type": "played", "live_state": null, "created_at": "2026-09-15T07:05:50.158384Z", "updated_at": "2026-09-18T10:59:10.806164Z", "changed_at": "2026-09-18T03:49:12.004518Z", "data_available": { "maps": true, "stats": true, "map_stats": true, "vetoes": true, "rounds": true, "depth": true, "lineups": true, "odds": false } }}{ "error": { "code": "match_merged", "message": "this match id was merged into 01a0d06b-4e06-7e01-905d-7384ae3c7ce1; use that id", "request_id": "01a12d10-6b2e-7d3a-9a4f-2c1d0e9b8a77" }}{ "error": { "code": "not_found", "message": "not found", "request_id": "019f3d18-c15f-7319-81a7-343e8a80a578" }}{ "error": { "code": "not_found", "message": "not found", "request_id": "019f3d18-c15f-7319-81a7-343e8a80a578" }}{ "error": { "code": "rate_limited", "message": "rate limit exceeded", "request_id": "019f3d18-c15f-7319-81a7-343e8a80a578" }}Per-player CS2 depth breakdowns for one match GET
The five CS2 depth breakdowns for one match, grouped into a single object: per-player weapons, grenades, hitgroups (body hit-group distribution), duels (the killer→victim kills matrix) and flashes…
What a match used to say, and when it stopped saying it GET
The changes made to a match's own fields, newest first, each with the value before and after.