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 NoneEvery 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
| Approach | Requests |
|---|---|
| Match, both teams, tournament, odds separately | 5 |
?include=teams,tournament,odds | 1 |
| Plus three detail layers, blind | 4 |
Plus three detail layers, gated on data_available | 0–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%.