How-to

Migrate from a scraper

Mapping the concepts you already have onto this API, and what changes.

If you're arriving from a scraped HTML pipeline, most of the work is mapping vocabulary. Here's the translation.

Concept mapping

Scraper conceptHere
Match page URLid (UUIDv7) or a list filter
Team page slugslug — works directly as a path id
Event pagetournament_id, or ?tournament= on the match list
Scoreboard table/matches/{id}/stats
Round history graphic/matches/{id}/rounds
Map veto box/matches/{id}/vetoes
Rating columnrating on a stat line — ours, not a third party's
Odds comparison tableA single de-vigged line; per-book prices are not served

What gets easier

No parsing, no breakage on redesign. Field names are a versioned contract locked by tests.

Pagination that doesn't skip rows. Cursors are stable under concurrent inserts, unlike ?page=N over a table that's being written to.

Coverage is declared. data_available on each match tells you which layers exist, and /v1/cs2/coverage states what the product does and doesn't have — including what's not built yet.

Change is pushed. A WebSocket subscription replaces a polling loop entirely.

What's different

No per-book odds. The API serves one de-vigged aggregate line (eo_market) with a book_count, never individual bookmaker prices or names. If your pipeline compared books, that's not something this API replaces — see the market line.

Requests are metered. A scraper's cost is bandwidth; here it's 20,000 requests a month. The habit to unlearn is polling everything on one timer — see budget your requests.

rating is ours. It is not the number you scraped from elsewhere and won't match it. Compare trends, not absolute values.

logo_url is always empty. We hold no redistribution rights to team marks.

A first migration

Map your entities. Walk /v1/cs2/teams and /v1/cs2/players once at limit=500 and store the UUIDs against whatever keys you already use. A few requests, done weekly.

Replace your fixture scrape with a cached /v1/cs2/matches window — see build a fixtures cache. The list row already carries team and tournament names, so most schedule rendering needs nothing else.

Replace per-match scrapes with ?include=teams,tournament,odds plus the detail layers you actually use, gated on data_available.

Replace your live poller with a WebSocket subscription. This is usually the biggest single saving.

Backfilling what you already have

You probably have history you don't want to re-fetch. Match ids won't line up with your old keys, so join on (scheduled_at, team names) for a one-off reconciliation, then store our UUIDs and use them from then on. Backfilling history covers pacing the walk.

On this page