Core concepts

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

EndpointFilters
/matchesstatus, tournament, team, date_from, date_to, has, changed_since
/teamsslug, region
/playersteam, slug, role, ids
/tournamentsslug, tier, region, year, status, event_type
/oddsmatch, source
/rankingstype, 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:

  • region is a full name, not a code: Europe, North America, South America, Asia, CIS, Oceania, Africa. ?region=EU matches nothing.
  • role is one of AWP, 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_type is lan or online; anything else is a 400. A tournament whose event_type is null (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 daydate_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.

On this page