guide

"CS2 API quickstart: your first call in five minutes"

Jul 10, 2026

Bottom line: You can go from zero to a live CS2 match feed in about five minutes. Sign up, put your key in an Authorization: Bearer header, and call GET /v1/cs2/matches. Every response is a JSON envelope — a data array or object plus a meta block — and the one thing that trips people up first is that list rows and detail rows return different shapes. This guide covers all of it, with runnable code in three languages, against the EsportsOdds CS2 data API.

What you'll build

By the end of this page you'll have made an authenticated request, pulled the list of Counter-Strike 2 matches, and read the fields out of the response. That's the foundation every other guide here builds on, from a live-scores dashboard to a Discord score bot.

The path from signing up to your first successful response: get a key, add it as a Bearer header, call GET /v1/cs2/matches, and read back a JSON envelope of data plus meta.

Step 1: Get an API key

Create an account on the dashboard and generate a key. Keys look like eo_live_… and are shown once, so store yours somewhere safe. Every example below reads it from an environment variable so you never paste a secret into code:

export ESPORTSODDS_API_KEY="eo_live_your_key_here"

There's a single plan — $99/month for 10,000 requests — and no separate keys to juggle for different endpoints. One key reaches the whole API.

Step 2: Make your first request

The base URL is https://api.esportsodds.gg, every path is versioned and namespaced by game, and CS2 lives under /v1/cs2/…. Authenticate by sending your key as a Bearer token:

curl -s https://api.esportsodds.gg/v1/cs2/matches \
  -H "Authorization: Bearer $ESPORTSODDS_API_KEY"

If the key is valid you'll get back a block of JSON. If it isn't, you'll get a flat {"error":"…"} with a 401 status — errors are always that simple one-key shape, never a nested structure.

Step 3: Read the envelope

Here's the part worth slowing down on. A list endpoint wraps its results in an envelope: the rows go in data, and a meta object tells you how many came back and whether there's another page.

{
  "data": [
    {
      "id": "0191f2c8-8a1e-7c3a-9f5e-2b6d4e8a1c00",
      "status": "completed",
      "team_a_name": "Natus Vincere",
      "team_b_name": "FaZe Clan",
      "score_a": 2,
      "score_b": 1,
      "tournament_name": "IEM Cologne 2026",
      "scheduled_at": "2026-07-08T17:00:00Z"
    }
  ],
  "meta": { "count": 1, "next_cursor": null }
}

The response envelope has two parts: a data array holding the item rows, and a meta object carrying count and next_cursor, which is null on the last page.

A single-resource endpoint (like one match by ID) returns {"data": { … }} — the same data key, but an object instead of an array, and no meta. Write your response handling around that split once and every endpoint behaves predictably.

Step 4: The one gotcha: list rows vs detail rows

The matches list you just called is an enriched projection. Each row already carries team_a_name, team_b_name, and tournament_name, so you can render a scoreboard straight from it with no extra lookups.

The single-match detail endpoint is deliberately leaner. GET /v1/cs2/matches/{id} returns a bare match with team_a_id and team_b_id — IDs, not names. If you need the names on a detail view, resolve the IDs against /v1/cs2/teams.

The matches list returns team and tournament names on every row; the single-match detail endpoint returns only IDs, which you resolve against the teams endpoint.

So: reach for the list when you want names, and hydrate IDs from /teams only on detail views. That single distinction saves a surprising number of redundant calls.

The same call in Python and JavaScript

Nothing above is curl-specific. In Python with requests:

import os
import requests

BASE = "https://api.esportsodds.gg"
key = os.environ["ESPORTSODDS_API_KEY"]

resp = requests.get(
    f"{BASE}/v1/cs2/matches",
    headers={"Authorization": f"Bearer {key}"},
    timeout=10,
)
resp.raise_for_status()
payload = resp.json()

for match in payload["data"]:
    a, b = match.get("team_a_name"), match.get("team_b_name")
    print(f"{a} vs {b}{match['status']}")

And in JavaScript with fetch:

const BASE = "https://api.esportsodds.gg";
const key = process.env.ESPORTSODDS_API_KEY;

const res = await fetch(`${BASE}/v1/cs2/matches`, {
  headers: { Authorization: `Bearer ${key}` },
});
if (!res.ok) throw new Error(`API error: ${res.status}`);

const { data } = await res.json();
for (const m of data) {
  console.log(`${m.team_a_name} vs ${m.team_b_name}${m.status}`);
}

Both read data the same way, because the envelope is the same everywhere.

Where to go next

You've got a working key and you understand the envelope. From here:

Start with a match list, confirm the shape, then build outward. The full endpoint reference has every resource you can reach with the key you just made.