Match lifecycle
What populates when, from scheduled through live to completed.
A match moves through three states, and which fields are trustworthy depends on where it is.
The states
status | Meaning | Scores | winner_team_id |
|---|---|---|---|
scheduled | Not started | null | null |
live | In progress | Partial | null |
completed | Finished | Final | Set, once the result is ingested |
cancelled | Called off — will not be played | null | null |
A cancelled match keeps its id and any odds history captured while it was scheduled, and stays
fetchable by id — but it never appears in upcoming listings. There is no postponed state: a
rescheduled fixture keeps scheduled and its scheduled_at moves.
scheduled_at is the planned start. There is no separate actual-start field, so a match that
started late still shows its scheduled time. Treat it as the fixture's slot, not as evidence of when
play began.
What appears when
Fixtures are created ahead of time with teams, tournament and format. Odds start being captured as soon as a market opens — often days before. Everything else lands as the match plays out or shortly after:
- Map results appear per map as each finishes.
- Round stats and player stats arrive with the map they belong to.
- Vetoes are known before play starts, since the pick/ban happens first.
- Depth (weapons, grenades, hitgroups, duels, flashes) is the last and least universal layer.
A completed match therefore does not guarantee every sub-resource exists.
Walkovers: empty is the right answer
Some completed matches were never played. A team forfeits, the fixture is awarded to their opponent,
and there is genuinely nothing to collect — no maps, no rounds, no player stats. result_type tells
you which kind of result you are looking at:
result_type | Meaning |
|---|---|
played | The match was played. |
walkover | Awarded on a forfeit. No map was played, so the empty sub-resources are correct, not missing. |
null | We were not told. Do not infer it from the score. |
Check it before reporting a completed match as a coverage gap. Within a played match, an individual
map can also be forfeited or abandoned — that is status on the
map result, not this field.
null is deliberate rather than lazy. A genuine 1–0 best-of-one whose map detail simply has not
arrived yet looks identical to a forfeit by score alone, so we leave the field unset rather than
assert something we cannot see.
Don't guess — read data_available
Every match row carries flags for exactly which sub-resources have data:
"data_available": {
"maps": true, "stats": true, "map_stats": false,
"vetoes": true, "rounds": true, "depth": false, "odds": true
}stats and map_stats both describe the /stats endpoint: stats is true when it returns
anything at all, map_stats only when it also returns the per-map rows rather than just the
whole-match aggregate. See player match stats.
Use them instead of speculatively fetching five endpoints and discarding empty responses. Each avoided request is one you keep in your monthly quota.
You can also filter the list to matches that have what you need:
# Only completed matches with round-level AND per-player depth data
curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
"https://api.esportsodds.gg/v1/cs2/matches?status=completed&has=rounds,depth&limit=20"has= accepts any comma-separated subset of maps,stats,map_stats,vetoes,rounds,depth,lineups,odds,
AND-ed together. An unknown value is a 400 rather than a silently ignored filter.
Settled odds
Once a match settles, its odds lines gain is_winner (which outcome came in) and one line per
outcome is flagged is_closing — the last price captured before the match began (its started_at
when we hold one, its scheduled_at when we don't). The closing line is the standard benchmark for
evaluating a forecast, so if you are scoring predictions, that's the row you want. Every line also
carries in_play, which separates pre-match prices from live ones. See
odds movement.
How fast it changes
Measured over a recent 7-day window, the whole fixture corpus takes about 6 writes an hour — matches simply don't change often. Odds move roughly 110× faster. Polling both on the same timer is the single most common way to burn a monthly quota; the arithmetic is in how often to poll.