Quickstart

From an API key to the next matches, their odds, and a WebSocket stream in four steps.

This walks the full path a typical integration takes: authenticate, list the next matches, pull the market odds for one, then (optionally) stream updates instead of polling. Every call is a plain GET (or one POST for the WebSocket ticket) over HTTPS, and every response is the same { "data": ..., "meta": ... } envelope with snake_case fields.

Prefer a client library?

Python developers can pip install esportsodds and skip the HTTP plumbing. The package reads ESPORTSODDS_API_KEY, walks pages for you, retries what is safe to retry and raises a typed exception for each error code. It uses only the standard library. See the official Python client.

1. Authenticate

Every request carries your key as a Bearer token. Create a key in the dashboard, then:

export EO_KEY="eo_live_..."
curl -H "Authorization: Bearer $EO_KEY" \
  "https://api.esportsodds.gg/v1/cs2/matches?status=completed&limit=1"

A 200 with one match in data means the key works: that is the most recent finished match.

See Authentication for the header, the deprecated ?apiKey= fallback, and how keys are stored.

No subscription yet?

A free sandbox key reads a fixed sample of past matches. The calls on this page ask for current data, so a sandbox key answers them with 403 (not counted): start from the calls in the sandbox guide instead, and swap in a paid key later.

2. List the next matches

curl -H "Authorization: Bearer $EO_KEY" \
  "https://api.esportsodds.gg/v1/cs2/matches?status=scheduled&sort=scheduled_at&has=odds&limit=5"

sort=scheduled_at puts the soonest match first (the default order is newest first, which for scheduled matches starts with the one furthest away), and has=odds keeps the matches that already have a market line, so step 3 has something to show.

{
  "data": [
    {
      "id": "018f...e2a1",
      "status": "scheduled",
      "format": "bo3",
      "team_a_id": "018f...aa10",
      "team_b_id": "018f...bb20",
      "team_a_name": "Natus Vincere",
      "team_b_name": "FaZe Clan",
      "tournament_name": "IEM Katowice 2026",
      "score_a": null,
      "score_b": null,
      "scheduled_at": "2026-07-01T18:00:00Z"
    }
  ],
  "meta": { "count": 1, "next_cursor": "eyJ..." }
}

Narrow with status, tournament, team, and date_from/date_to; page with limit + cursor (pass meta.next_cursor back as ?cursor=). Keep a match id for the next step.

status=live lists the matches being played right now. It returns an empty data when nothing is on, which is common between about 04:00 and 08:00 UTC, so do not build a first test around it.

3. Get the market odds

curl -H "Authorization: Bearer $EO_KEY" \
  "https://api.esportsodds.gg/v1/cs2/odds?match=018f...e2a1&latest=1&market=match_winner"

latest=1 returns the current price for each outcome. Leave it off and you get the match's whole line history instead, a page at a time.

{
  "data": [
    { "source": "eo_market", "outcome_key": "home", "label": "Natus Vincere", "price": 1.86, "book_count": 4, "match_id": "018f...e2a1", "captured_at": "2026-07-01T15:05:00Z" },
    { "source": "eo_market", "outcome_key": "away", "label": "FaZe Clan",    "price": 1.98, "book_count": 4, "match_id": "018f...e2a1", "captured_at": "2026-07-01T15:05:00Z" }
  ],
  "meta": { "count": 2, "next_cursor": null }
}

Each line carries an explicit source: eo_market is the de-vigged multi-book line (with book_count); eo_model is our modeled line once it clears validation. Add ?source=eo_market to request just one. Contributing books are never named — see the methodology.

4. Stream updates instead of polling

Once a match is being played, open the WebSocket channel and let updates come to you rather than re-polling steps 2–3 on a timer. It's a two-step ticket handshake so your key never appears in a socket URL:

curl -X POST -H "Authorization: Bearer $EO_KEY" \
  "https://api.esportsodds.gg/v1/cs2/ws-token"
# → { "data": { "token": "eyJ...", "expires_at": "..." } }

Open the socket, subscribe to the match, and you'll get a full snapshot followed by incremental update frames. Every frame is a { type, match_id, seq, data } envelope:

const ws = new WebSocket(`wss://api.esportsodds.gg/v1/ws?token=${token}`)

ws.addEventListener('open', () => {
  ws.send(JSON.stringify({ type: 'subscribe', data: { match_ids: ['018f...e2a1'] } }))
})

ws.addEventListener('message', (e) => {
  const frame = JSON.parse(e.data)
  if (frame.type === 'snapshot') setState(frame.match_id, frame.data) // full baseline
  if (frame.type === 'update') applyUpdate(frame.match_id, frame.data) // a delta
})

The full message types, sequence/reconnect semantics, and connection metering are covered in Live data & WebSocket.

Next steps

  • Authentication · Rate limits · Errors
  • The full interactive endpoint reference — with a "try it" panel per endpoint — is in the sidebar under API reference.

On this page