Filtering and lookups
The filters each list accepts, how they combine, and resolving a name to an id.
Every list filter is applied server-side and costs the same single request as an unfiltered call — so filtering is always cheaper than fetching broadly and discarding rows client-side.
Filters combine with AND
curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
"https://api.esportsodds.gg/v1/cs2/matches?status=completed&date_from=2026-07-01&date_to=2026-07-31&has=rounds"There is no OR, no negation and no field-expression syntax. Every filter is an exact match on one field, and they narrow together.
What each list accepts
| Endpoint | Filters |
|---|---|
/matches | status, tournament, team, date_from, date_to, has, changed_since |
/teams | slug, region |
/players | team, slug, role, ids |
/tournaments | slug, tier, region, year, status, event_type |
/odds | match, source |
/rankings | type, region, role |
/matches/bulk takes the same filters as /matches. /matches, /teams, /players and
/rankings also accept ?sort= — see pagination.
Values are exact and case-sensitive. A few that catch people out:
regionis a full name, not a code:Europe,North America,South America,Asia,CIS,Oceania,Africa.?region=EUmatches nothing.roleis one ofAWP,IGL,Lurker,Rifler,Support— and only about 14% of players have one on file, so the filter excludes every player whose role is simply unknown.event_typeislanoronline; anything else is a400. A tournament whoseevent_typeisnull(not stated) matches neither value.
Turning a name into an id
You rarely start with a UUID. Two ways to get one:
Address the resource by slug directly. Teams, players and tournaments accept a slug wherever a path id is expected:
curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
"https://api.esportsodds.gg/v1/cs2/teams/natus-vincere"Or filter the list by slug when you want the row in list shape:
curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
"https://api.esportsodds.gg/v1/cs2/teams?slug=natus-vincere"?slug= returns the single matching row — or an empty page — in the normal list envelope, and never
depends on where the row falls in the paging order. Matches have no slug, so a match id must be a
UUID.
See resolving by slug for the pattern in code.
Batch lookups
/players?ids= takes 1–500 comma-separated ids in one request. After reading a match's stat lines
you have ten player ids; resolve them all at once rather than ten times:
curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
"https://api.esportsodds.gg/v1/cs2/players?ids=019f28e1-e6ae-7032-8255-4c7e26548918,019f28e1-e6af-730c-a332-402b928fe87f"Outside the 1–500 range is a 400.
Dates
date_from and date_to are YYYY-MM-DD, compared against scheduled_at in UTC, and both are
inclusive of the whole day — date_to=2026-07-01 includes everything on 1 July. A malformed date
is a 400, never a silently ignored filter.
Syncing changes
changed_since is an RFC 3339 timestamp (2026-09-18T00:00:00Z) and returns matches whose
changed_at is at or after it. Pair it with sort=changed_at and page forward:
curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
"https://api.esportsodds.gg/v1/cs2/matches?changed_since=2026-09-18T00:00:00Z&sort=changed_at&limit=100"changed_at moves for two reasons. The first is a customer-visible field of the match itself —
status, scores, winner, schedule, start and finish times, stage, format, result type, teams or
tournament. The second is a sub-resource arriving for the first time: the moment one of the
data_available flags turns true because maps, stats, per-map stats, vetoes, rounds, depth or
lineups landed. That second half matters because most of them land hours or days after a match
finishes, and before 2026-09-18 the feed never mentioned it.
Re-fetching or correcting a sub-resource you already hold does not move it again — only the
transition from "none" to "some" does — so a nightly backfill pass does not put the whole archive
back in your feed. Don't use updated_at as a cursor: it advances on every refresh pass whether or
not anything changed.
The bound is inclusive, so re-reading from your last-seen instant can show you a row twice but never
skip one. Overlap successive windows by about ten minutes and de-duplicate on id. With
changed_since present and no status filter, cancelled matches are included, so a fixture you
already hold can be seen to cancel. The feed starts on 2026-09-18; a malformed timestamp is a 400.