guide

"Build CS2 fantasy projections from player stats"

Jul 10, 2026

Bottom line: A CS2 fantasy projection is career averages fed through a scoring function you define. The API gives you the inputs — avg_rating, avg_adr, avg_kast, kd_ratio — per player. The one step people miss is that the career-stats route needs a player UUID, not a slug, so you resolve the slug first. The scoring weights are yours to choose; this guide gives you a sensible starting point and the plumbing around it, built on the EsportsOdds CS2 data API.

The pipeline

Four steps: turn a slug into an ID, pull that player's career stats, run them through a scoring function, and you have a projection.

The projection pipeline: resolve a slug to a UUID, GET the player's career stats, apply your scoring weights, and produce a projected number.

Step 1: Resolve the slug to an ID

Detail routes on the API take UUIDs. You'll usually start from a human-friendly slug like s1mple, so resolve it against the players list, which accepts an exact ?slug=:

import os
import requests

BASE = "https://api.esportsodds.gg"
HEADERS = {"Authorization": f"Bearer {os.environ['ESPORTSODDS_API_KEY']}"}


def player_id(slug: str) -> str:
    resp = requests.get(f"{BASE}/v1/cs2/players", params={"slug": slug},
                        headers=HEADERS, timeout=10)
    resp.raise_for_status()
    rows = resp.json()["data"]
    if not rows:
        raise LookupError(f"no player with slug {slug!r}")
    return rows[0]["id"]

This slug-to-ID hop is the same one you use for teams and matches — any time a route wants an ID and you have a name, the list endpoint with ?slug= is the resolver.

Step 2: Pull career stats

Now fetch the career averages from /players/{id}/stats. This is a single-resource call (not paginated), and it returns the season-long numbers:

The career-stats response gives you matches played plus averages: rating, ADR, KAST, and a K/D ratio — the inputs to a projection.

def career(slug: str) -> dict:
    pid = player_id(slug)
    resp = requests.get(f"{BASE}/v1/cs2/players/{pid}/stats",
                        headers=HEADERS, timeout=10)
    resp.raise_for_status()
    return resp.json()["data"]

The fields you'll use are avg_rating, avg_adr, avg_kast, kd_ratio, and matches (how many games back the averages). The averages are nullable — a player with too little history may return null for some — so treat missing values defensively.

Step 3: Score it

Here's where your fantasy format's rules live. The weights below are a reasonable default that leans on rating (the broadest measure of impact) and rewards consistency via KAST, but tune them to your league:

def project(stats: dict) -> float:
    rating = stats.get("avg_rating") or 1.0
    adr = stats.get("avg_adr") or 70.0
    kast = stats.get("avg_kast") or 0.70   # 0–1 fraction
    kd = stats.get("kd_ratio") or 1.0

    # Weights are illustrative — set these to match your scoring rules.
    return round(
        rating * 12.0      # overall impact
        + adr * 0.10       # damage output
        + kast * 8.0       # round-to-round consistency
        + (kd - 1.0) * 5.0,  # duel win rate, centred on even
        1,
    )

The or <default> guards matter: they keep a null average from crashing the arithmetic, falling back to a roughly league-average value instead.

Step 4: Rank a roster

Project a list of players and sort:

ROSTER = ["s1mple", "zywoo", "sh1ro", "donk", "m0nesy"]

projections = []
for slug in ROSTER:
    try:
        projections.append((slug, project(career(slug))))
    except LookupError as e:
        print(f"skipping: {e}")

projections.sort(key=lambda x: x[1], reverse=True)
for slug, points in projections:
    print(f"{slug:10s} {points:6.1f}")

The output is a ranked slate you can drop into a draft tool or a lineup optimiser:

An example projected roster: five players ranked by projected fantasy points derived from their career averages.

A note on honesty

A projection built from career averages is a baseline, not a forecast of a specific match. It doesn't know about a roster change last week, a map that suits one team, or who's stood in as a substitute. Treat it as a starting estimate and layer context on top — recent form is a good next input. Present it as "projected from career averages," which is exactly what it is.

Next steps

Resolve the slug, pull the averages, apply your weights, and rank. The endpoint reference documents every career-stats field and which are nullable.