Documentation Index
Fetch the complete documentation index at: https://docs.ticktock.bet/llms.txt
Use this file to discover all available pages before exploring further.
Why scopes
Same URL, different field set per caller. A bookmaker on cs2.odds.basic calling GET /cs2/v1/matches/{id} gets the markets and rounds. A stats consumer on cs2.stats calling the same URL gets the stats but no markets. An internal operator calling it gets everything plus operational fields. One URL, one serializer, scope-filtered output.
Use /v1/whoami to introspect what your key currently grants.
Bundles
Bundles are issuance shorthand — pick one (or several) at key creation, the system expands them to a flat scope list. There is no “bundle” entity in the database — just the resulting scope set on your key.
| Bundle | Use case | Includes |
|---|
cs2.odds.basic | Bookmakers consuming odds + settlements | matches list/read/detail, markets read, settlements, WS for matches & markets, tournaments, teams, players |
cs2.odds.scoreboard-addon | Add-on to odds.basic for live scoreboard | live HP/money/weapon scoreboard + WS scoreboard frames (front-running risk; priced separately) |
cs2.stats | Stats-only consumers | matches list/read/detail, tournaments, teams, players, maps. No odds, no live frames. |
cs2.scoretock | Public-portal partners (finished matches only) | matches list/read/detail, teams/players/tournaments read |
cs2.operator | Internal services and our own UIs | every cs2:*:* scope. Issued only to internal systems. |
Granular scopes
Format: cs2:{resource}:{action}. Keys can also be issued with raw scopes a la carte (e.g. add cs2:matches:scoreboard to a stats key on request).
| Scope | Grants |
|---|
cs2:matches:list | Match list — basic shape (id, teams, event, status, scheduled_at, score) |
cs2:matches:read | Single-match read — same shape as list, scoped to one match |
cs2:matches:detail | Adds rounds, events, timeline, demo URL, streams (with VOD anchors), per-player stats |
cs2:matches:scoreboard | Adds live scoreboard (HP/money/weapons). Front-running risk — gated explicitly |
cs2:matches:internals | Operator-only: integration IDs, data_delay_seconds, suspended, booking_status, source_url |
cs2:markets:read | Active market offers, odds, suspend status |
cs2:markets:settlements | Settlement audit log |
cs2:tournaments:list / cs2:tournaments:read | Tournament directory and detail |
cs2:teams:list / cs2:teams:read | Team directory, profile, form, h2h |
cs2:players:list / cs2:players:read | Player directory and profile |
cs2:maps:read | Global map analytics + active pool |
cs2:stream:matches | WS frames: match.*, round.*, event.* |
cs2:stream:markets | WS frames: market.*, bet_settlement |
cs2:stream:scoreboard | WS frames: scoreboard.frame |
Scope → fields
The same URL emits different fields depending on which scopes you have. Fields outside your scope set are simply absent from the response — not nulled, just not emitted.
Example: GET /cs2/v1/matches/{id}
| Field group | Required scope |
|---|
id, team_a, team_b, event, bo_type, status, scheduled_at, score_a/b | cs2:matches:list or cs2:matches:read |
started_at, finished_at, maps[] | cs2:matches:read |
rounds[], events[], streams[], demo_url, player_stats[] | cs2:matches:detail |
markets[] | cs2:markets:read |
scoreboard | cs2:matches:scoreboard |
oddin_match_id, data_delay_seconds, suspended, … | cs2:matches:internals |
The same logic applies to Round, Stream, Market, and other shapes. Detailed mappings live alongside the corresponding endpoint pages in the API Reference.
Content negotiation
Every REST endpoint serves both JSON (default) and XML. Pass Accept: application/xml and the response body comes back as XML on the same URL. The seven AMQP-feed endpoints additionally expose a .xml sibling URL for clients whose HTTP infrastructure prefers a distinct cache key per format.
Two XML dialects
| Dialect | Endpoints | Schema |
|---|
| Feed XML (hand-tuned, byte-compatible with AMQP feed) | The seven endpoints below | Oddin-style envelopes. Stable wire format. |
| Stats XML (mechanical JSON→XML) | Everything else (teams, players, tournaments, maps, per-match analytics, settlements, search, sports catalog) | Predictable: dict → element with attribute children; list → wrapper with singularised item tags; null → element absent. |
Feed XML endpoints
These use hand-tuned envelopes that match the AMQP feed byte-for-byte — useful for clients that already have an Oddin/Sportradar-style XML pipeline.
| Endpoint | XML root element |
|---|
GET /v1/alive | <alive> |
GET /v1/whoami | <bookmaker_details> |
GET /cs2/v1/matches | <schedule> |
GET /cs2/v1/matches/{id} | <fixtures_fixture> (compact, not the rich JSON detail) |
GET /cs2/v1/matches/{id}/summary | <match_summary> |
GET /cs2/v1/matches/{id}/markets | <odds_change> over active B2B-feed offers |
GET /cs2/v1/markets/descriptions | <market_descriptions> |
Stats XML
Every other endpoint produces a mechanical XML rendering of the JSON shape. Example: GET /cs2/v1/teams/11712 with Accept: application/xml returns:
<team>
<data hltv_team_id="11712" name="Sashi" world_ranking="42" country_code="DK" rating="1.04">
<map_stats>
<map_stat map_name="de_mirage" matches_played="18" win_pct="0.55"/>
<map_stat map_name="de_inferno" matches_played="12" win_pct="0.50"/>
</map_stats>
</data>
</team>
Rules: scalars become attributes, nested dicts become child elements, lists become wrapper elements with singularised item tags, null is elided. There is no XSD for this dialect today — consumers parse it via XPath / element-tree.
Pass the Accept: application/xml header on any GET, or use the .xml URL form on the seven feed endpoints. Errors (4xx/5xx) keep the JSON {"detail": "..."} shape regardless of Accept so error parsing stays uniform.
WebSocket frames are JSON only.