How-to

Fetch everything about one match

One request instead of five, and how to skip the sub-resources that don't exist.

A match view usually needs the fixture, both teams, the tournament and the odds. Done naively that's five requests. It can be one.

Use include=

curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
  "https://api.esportsodds.gg/v1/cs2/matches/019f3d18-c15f-7319-81a7-343e8a80a578?include=teams,tournament,odds"

The response gains team_a, team_b, tournament and odds alongside the usual match fields. Without include= those keys are absent, not null, so existing clients are unaffected.

Unknown expansion names are ignored rather than rejected — adding one later can't break you.

Then fetch only the sub-resources that exist

include= covers the related entities. The CS2 detail layers are separate endpoints, and not every match has every layer. Read data_available first:

m = get(f"/v1/cs2/matches/{match_id}?include=teams,tournament,odds")["data"]

print(f"{m['team_a']['name']} vs {m['team_b']['name']}{m['tournament']['name']}")

avail = m["data_available"]
maps   = get(f"/v1/cs2/matches/{match_id}/maps")["data"]   if avail["maps"]   else []
stats  = get(f"/v1/cs2/matches/{match_id}/stats")["data"]  if avail["stats"]  else []
rounds = get(f"/v1/cs2/matches/{match_id}/rounds")["data"] if avail["rounds"] else []
depth  = get(f"/v1/cs2/matches/{match_id}/depth")["data"]  if avail["depth"]  else None

Every skipped call is a request you keep. Requesting a layer that doesn't exist isn't an error — it returns 200 with an empty list — but it still costs you one.

Find matches that have what you need

If you're building a dataset rather than rendering one match, filter at the list level so you never fetch a match you'd discard:

curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
  "https://api.esportsodds.gg/v1/cs2/matches?status=completed&has=rounds,depth&limit=100"

has= takes any subset of maps,stats,vetoes,rounds,depth, AND-ed.

Don't double-count the stats

/stats returns one row per player per map plus a whole-match aggregate row per player, with map_number: null on the aggregate:

per_map   = [s for s in stats if s["map_number"] is not None]
aggregate = [s for s in stats if s["map_number"] is None]

Summing everything counts each player twice. See player match stats.

Cost comparison

ApproachRequests
Match, both teams, tournament, odds separately5
?include=teams,tournament,odds1
Plus three detail layers, blind4
Plus three detail layers, gated on data_available0–3

For a page rendering 20 matches that's the difference between 100 requests and 20 — 0.5% of your month instead of 2.5%.

On this page