Core concepts

IDs, slugs and time

UUIDv7 identifiers, human-readable slugs, and the timestamp formats the API uses.

Identifiers

Every resource id is a UUIDv7 — canonical, 36 characters, lowercase and hyphenated:

019f3d18-c15f-7319-81a7-343e8a80a578

UUIDv7 embeds a millisecond timestamp in its leading bits, so ids sort roughly by creation time. That is an implementation detail, not a contract: don't parse a timestamp out of an id or infer ordering from one. It does mean ids from the same era share a visible prefix (019f…), which is a handy sanity check that you're looking at our data.

Ids are validated by format, not by version — a v4 UUID is accepted as well-formed and then simply matches nothing.

Slugs

Teams, players and tournaments also have a slug: lowercase, hyphenated, unique within the game.

natus-vincere      s1mple      pgl-major-singapore-2026

A slug works anywhere a path id does, so you can address a resource straight from a name you already have. Matches have no slug.

Slugs are stable but not guaranteed permanent — an organisation rename can change one. If you're storing a reference for the long term, store the UUID and treat the slug as a display convenience.

A player's nickname is not an identifier

Two different people can compete under the same handle, and they do: the CS2 corpus currently holds 109 nicknames worn by more than one player. nickname is a display name, nothing more — only id (and, within a game, slug) identifies a player.

So if you key your own tables on a nickname, two careers will eventually merge into one row on your side. Three practical consequences:

  • A roster or include=players response can legitimately contain two players with the same nickname and different ids.
  • Matching a player across your data and ours by nickname will sometimes match the wrong person. Match on id, or on slug if you're working from our data already.
  • A player who changes handle keeps the same id. Their nickname and slug change; nothing else does, and their match history stays on the one id.

When two ids turn out to be one player

Rarely, we find that we have stored one person twice — the two rows come from upstream records we had no way to connect until they were connected for us. When that happens we merge them: one id keeps the full history, and the other is retired and stops resolving. GET /players/{id} on a retired id returns 404, and it disappears from lineups, rosters and stat lines, where the surviving id now appears instead.

This is uncommon — eight players across the whole CS2 archive as of September 2026 — but if you store player ids and one starts returning 404, a merge is the likely reason. Re-resolve it by slug or by nickname plus team and you will land on the surviving row. Every stat, duel and roster entry that named the retired id was moved, not deleted; nothing is lost but the id itself.

Timestamps

Every timestamp is RFC 3339 in UTC, with a trailing Z:

{ "scheduled_at": "2026-07-04T15:36:36.403882Z" }

Sub-second precision appears where the source has it (odds captures) and is absent where it doesn't (scheduled fixtures). Parse with a standard RFC 3339 parser rather than a fixed-width format string — time.Parse(time.RFC3339, …), datetime.fromisoformat(…), new Date(…) all handle both.

There is no timezone parameter. Everything is UTC; convert at the display layer.

Date-only parameters

Filters that take a day rather than an instant use YYYY-MM-DD:

?date_from=2026-07-01&date_to=2026-07-31

Both bounds are inclusive of the entire day in UTC. X-Quota-Reset is the one header that carries a full RFC 3339 instant rather than a date.

Nulls

A nullable field is null, never omitted and never an empty string — except where the API deliberately uses "" for "no value on file" (short_name, real_name, nationality). Arrays are [] rather than null when empty. So null always means "no value", and you never have to distinguish "absent" from "explicitly nothing".

On this page