guide
"Read the CS2 market odds line via the API"
Jul 10, 2026
Bottom line: The CS2 odds endpoint serves one derived line —
eo_market, an aggregate combined from multiple bookmakers and exchanges with the margin already removed. You pull the current snapshot, or a single match's line history, and convert each decimalpriceinto an implied probability with1 / price. Because the margin is stripped, the two sides of a market sum to about 100% with nothing left to de-vig yourself. No individual book is ever named. Built on the EsportsOdds CS2 data API.
What the odds endpoint serves
A quick framing point, because it shapes everything below. The API does not resell individual bookmaker prices. It serves a derived aggregate, eo_market, built from several sources with the bookmaker margin removed. Each line tells you how many sources fed it via book_count, but the sources themselves are never named. (A separate proprietary model line, eo_model, is in the works and gated until it clears validation; this guide uses the market line.)
Two ways to read it
GET /v1/cs2/odds has two modes. Without a match parameter it returns the current snapshot — the latest line for every open market, in one unpaginated response. With ?match={id} it returns that one match's line history, newest first, which is paginated.
Start with the snapshot, filtering to the market source explicitly:
import os
import requests
BASE = "https://api.esportsodds.gg"
HEADERS = {"Authorization": f"Bearer {os.environ['ESPORTSODDS_API_KEY']}"}
def snapshot() -> list[dict]:
r = requests.get(f"{BASE}/v1/cs2/odds", params={"source": "eo_market"},
headers=HEADERS, timeout=15)
r.raise_for_status()
return r.json()["data"]
The source parameter must be eo_market (or eo_model once it's live) — anything else returns a 400. Each row carries a label (the outcome, e.g. a team name), an outcome_key (home / away for a match winner), a decimal price, a book_count, and a captured_at timestamp.
Convert price to implied probability
Decimal odds and probability are two views of the same number. The implied probability of a decimal price is simply its reciprocal:
def implied(price: float) -> float:
return 1.0 / price
Here's why that's all you need. On a raw bookmaker board, the two sides of a match-winner market add up to more than 100% — that excess is the margin. The eo_market line already has it removed, so the two implied probabilities sum to roughly 100% on their own.
def match_winner_probs(lines: list[dict]) -> dict[str, float]:
winner = [ln for ln in lines if ln["outcome_key"] in ("home", "away")]
return {ln["label"]: round(implied(ln["price"]) * 100, 1) for ln in winner}
# e.g. {"Natus Vincere": 63.2, "FaZe Clan": 36.8} — sums to ~100
The result is a clean market-implied win probability for each side. Present it as exactly that: "market-implied," a neutral read of where the aggregate sits, not as a recommendation.
Track how a line moves
Pass a match ID and you get that match's history, so you can watch the line evolve as a game approaches. This mode is paginated, so use the cursor loop from the pagination guide:
def line_history(match_id: str) -> list[dict]:
rows, params = [], {"source": "eo_market", "match": match_id, "limit": 500}
while True:
r = requests.get(f"{BASE}/v1/cs2/odds", params=params, headers=HEADERS, timeout=15)
r.raise_for_status()
payload = r.json()
rows.extend(payload["data"])
cursor = payload["meta"].get("next_cursor")
if not cursor:
return rows
params["cursor"] = cursor
history = line_history("0191f2c8-8a1e-7c3a-9f5e-2b6d4e8a1c00")
one_side = [ln for ln in history if ln["outcome_key"] == "home"]
for ln in sorted(one_side, key=lambda x: x["captured_at"]):
print(f"{ln['captured_at']} {implied(ln['price']) * 100:5.1f}% ({ln['book_count']} sources)")
Charted, that history shows the market-implied probability drifting as the match nears — the kind of movement analysts track for closing-line value:
The delta_since_open and open_price fields on each row give you the move relative to the opening line without recomputing it, and is_closing flags the final captured line.
A few things to keep straight
- Never surface a source. The line is an aggregate by design;
book_countis the honest way to convey depth, and no individual book is named — keep it that way in whatever you build. - Prices are decimal odds. Convert to probability with
1 / price; don't assume a different format. - The market line is de-vigged; a raw board isn't. If you ever mix in prices from elsewhere, those still carry a margin — this line doesn't.
Next steps
- Build a resilient client — line-history pulls add up, so back off politely.
- Show it on a dashboard — render the implied probability next to the live score.
Pull the snapshot, convert with 1 / price, and track the history for movement. The endpoint reference documents every field on an odds line.