CS2 data reference

Ratings and rankings

Glicko-2, why the board sorts on a discounted rating, and the sample floors.

Two related surfaces: a team's rating over time, and the leaderboard built from it.

Rating history

GET /v1/{game}/teams/{id}/ratings returns {rating, rd, as_of, match_id} points, oldest-first so the series plots directly. Default 30 points, max 1000; one point per match the team played.

Naming a window (6m, up to 60m, or all) returns that whole span and raises the default to the maximum, so ?window=12m is a year of points in one call. match_id is the match that moved the rating to that point — join it to /teams/{id}/results to say what happened there.

rating is Glicko-2. rd is the rating deviation — the model's uncertainty. It falls as a team plays and rises during inactivity, so a returning team has a high rd and its rating should be read as provisional.

The leaderboard

GET /v1/{game}/rankings?type=team|player over a rolling 3-month window.

Teams rank on glicko2_conservative — the rating discounted by twice its deviation:

value = rating − 2 × rd

That's the important design decision. A team with a high rating the model isn't confident about sorts below an equally-rated team it is confident about. It rewards demonstrated form over a lucky run, and it means an inactive team drifts down as its rd grows rather than sitting on a stale rating forever.

Players rank on rating. Each row reports which via its metric field, so you never have to infer it.

Sample floors

Entities below a minimum sample are omitted entirely rather than ranked on noise:

  • Teams: at least 5 rated matches in the window.
  • Players: at least 30 rated maps. A map counts once, from its own per-map rating, and only if it was played on a competitive map; the whole-match summary stored beside a player's maps is not counted.

So absence from the board means "not enough evidence", not "ranked last".

Row fields

rank is 1-based board position. Sorting rows by value reproduces rank exactly.

Teams additionally get:

  • form — up to 5 recent results as ["W","L",…], newest first.
  • rank_delta — movement versus roughly 7 days ago, or delta_days ago (1–90) when you ask. Positive means moved up. null means the team wasn't on the board then, which is different from "didn't move" (0). The earlier board is rebuilt from the ratings as they stood then, with the deviation inflated for the time since — the same rule as the live board.
  • trend — only with ?include=trend: the board metric at each of the last twelve weekly marks, oldest first, ending with the value now. An entry is null for a mark where the team had no rating. Absent, not null, when you do not ask.
  • rating and rd — only with ?include=rating: the Glicko-2 rating and its deviation, each to a tenth, where value is rating - 2 * rd. The deviation is as of now, so it grows while a team is idle — the same step that discounts value for a team that has not played lately. Draw rating ± 2 * rd as a range without a call per team. Absent, not null, when you do not ask, and never on the player board.
  • external_rank — a separately-sourced world ranking where one exists, reported alongside for comparison. It is not used for ordering.

Sorting and paging

/rankings accepts sort=rank|value|name with a - prefix for descending, and is not cursor-paginated — limit truncates the computed board.

Re-sorting changes the order rows come back in, never the rank each entity holds. So ?sort=-rank&limit=10 returns the bottom ten with their real ranks, rather than renumbering them 1–10.

The board over time

GET /v1/{game}/rankings/history?weeks=13&top=10 returns the top of the team board at each of the last weeks weekly marks (1–52, default 13), oldest first, newest being now. Each snapshot lists the top top teams (1–25, default 10) as {rank, team_id, name, slug, value}.

Each snapshot is the board /rankings would have served at that instant — the latest rating then, its deviation inflated for the idle time since, and the same activity floor of 5 rated matches in the three months ending then — so a team appears only if it was active at the time. You always get exactly weeks snapshots; a mark at which no team met the floor has an empty entries list. It is computed once per ten minutes and shared across callers, so the newest as_of can be that old.

Round-level figures for the top teams

GET /v1/{game}/rankings/round-stats returns the figures of /teams/{id}/round-stats for the top teams of the board in board order, each with its team and rank, in one request. window is a trailing span (default 6m, maximum 12m, no all) and limit is the number of top teams (default 30, max 100). A team with no recorded rounds in the window reports rounds: 0 and null rates. Also computed once per ten minutes.

Own rank on a detail route

A team or player detail row can carry own_rank, but only when you ask:

curl -H "Authorization: Bearer $ESPORTSODDS_API_KEY" \
  "https://api.esportsodds.gg/v1/cs2/teams/natus-vincere?rank=1"

It costs a window query over the whole board, so it is off by default. Without the parameter, own_rank is null meaning "not requested" — not "unranked".

On this page