/cs2/v1/* API.
Use a base URL ending in /v1/, for example:
Sport & category model
The facade follows the UOF hierarchy Sport → Category → Tournament → Match. The sport is one per integration instance (provider_default_sport_id,
default "2" = CyberSport). It is not a record in the REST sports
catalog — that catalog returns categories. The sport id rides only the
AMQP routing key (its sport section), where the client scopes match-pairing
to one sport.
So for CS2: the AMQP routing-key sport section is
2 (CyberSport); the REST
sports catalog and the nested <sport> a tournament carries are the CS2
category (id="3", folded into the client’s category_id). Both the routing
key and the category id stay stable, so match pairing — which searches
candidates within one sport — lines up.
Football is served now under the Soccer sport (id
1) with per-country
categories — see the Football section. Events classify
under Soccer with the country as the category (nested in the tournament), matching
the client’s football model so fuzzy match-pairing resolves. Data, the REST
catalog, the XML feed, and the live lifecycle are live; market generation is the
remaining piece.Authentication
These routes accept the tenant key in eitherX-API-Key or the UOF-standard
x-access-token header — send x-access-token: <your-key>, exactly as against
the upstream UOF REST API. (A legacy $x$access-token spelling is also still
accepted for backward compatibility, but prefer x-access-token.) All names
enter the same authentication path, so scopes, sport_allowlist, key
revocation, and rate limiting behave identically. If more than one is sent,
their values must match.
For the current CS2 integration, issue the cs2.odds.basic bundle and include
cs2 in sport_allowlist; that bundle includes match, market, tournament,
team, player, settlement, stream, and recovery access. A future second sport
must add its own scope bundle and sport slug to the same key when that sport’s
API is implemented.
Locales
Every catalogue and detail request consumes the locale from its path. Onlyen and ru are supported; unknown locale values return HTTP 400.
Entity IDs, qualifiers, status codes and other machine-readable values are
locale-independent. A localized request never returns an undefined or empty
display value merely because a Russian translation is unavailable.
Resource paths
All paths below are relative to the/v1/ base URL:
Content-Type: application/xml. Empty collections return HTTP 200 with an
empty valid XML container. Unknown individual resources return HTTP 404. For
the catalogue-detail endpoints the 404 body is an XML not-found marker — not
a JSON {detail} envelope a strict XML client would crash on:
- Missing tournament →
<tournament_info><response response_code="NOT_FOUND"/></tournament_info> - Missing competitor →
<competitor_profile><response response_code="NOT_FOUND"/></competitor_profile> - Missing sport-event summary →
<match_summary><response response_code="NOT_FOUND"/></match_summary> - Missing sport-event fixture →
<fixtures_fixture><response response_code="NOT_FOUND"/></fixtures_fixture> - Missing player →
<player_profile><response response_code="NOT_FOUND"/></player_profile> - Missing market variant →
<market_descriptions><response response_code="NOT_FOUND"/></market_descriptions>
{detail}.
GET sports/:locale/tournaments/:id/info returns <tournament_info> with a
nested <tournament> carrying id, name, risk_tier, scheduled,
scheduled_end, an optional icon_path, and a nested <sport>.
GET sports/:locale/competitors/:id/profile returns <competitor_profile>
with a nested <competitor> carrying id, name, an optional icon_path,
and country_code. The same competitor id appears as the home/away
<competitor> in event payloads, so the payload reference and the profile
lookup share one key.
GET sports/:locale/sport_events/:id/fixture returns <fixtures_fixture> and
GET sports/:locale/sport_events/:id/summary returns <match_summary>. Both
envelopes carry the two teams as nested <competitor> elements with a
qualifier (home / away), and an <extra_info> block with at least
<info key="sport_format" value="classic"/> (the fixture also adds
best_of). Clients that admit a match by its competitor qualifiers and read
sport_format from <extra_info> find both on either envelope.
GET sports/:locale/sports returns <sports> with one <sport> entry per
category (today: CS2). Each entry carries the bare integer id (3 for
CS2 — the value the client stores as its category_id), name, and
icon_path. In the client’s Sport → Category → Tournament → Match hierarchy
CS2 is the category; the sport itself (e.g. CyberSport) is the client’s own
configured default, not an entry here. icon_path always points to a
platform-hosted asset. The default is
https://ticktock.bet/categories/cs2.svg; deployments on another public
domain can override it with CS2_CATEGORY_ICON_URL.
GET descriptions/:locale/markets returns <market_descriptions> with one
<market> per type, each carrying nested <outcomes> (every CS2 market has
one <outcome id="1" name="…">) and <specifiers>
(<specifier name="…" type="integer|variable_text|decimal"/>). The response is
single-locale: every display string (<market name>, <outcome name>)
appears once, in the path’s :locale, with no name_ru sibling — fetch
the ru path for Russian. (description stays English until a Russian source
exists.) Those name attributes are parametrized templates: substitute
{placeholder} tokens (e.g. {round}, {player}) from the live market’s
specifiers — they are not pre-rendered static labels. A live <odds_change>
outcome is unnamed and its market carries only id + specifiers, so clients
name the outcome from the catalog’s <outcome> list and render the template’s
placeholders only for the declared <specifier> keys.
GET descriptions/:locale/markets/:market_id/variants/:variant_urn resolves a
market whose outcome set varies match-to-match (BB 3.8) — a live market
flags it with a variant=<urn> specifier, and this resource returns that
variant’s outcomes (same nested <market variant="…"><outcomes> form, stable
ids, per-locale template names). Ticktock has no variable-outcome markets today
(all CS2 outcome sets are fixed), so this currently returns the market’s
declared outcomes; the dynamic per-variant roster (e.g. a football
first-goalscorer) lands with the football build.
GET descriptions/:locale/match_status returns the match-status dictionary the
client uses to label <sport_event_status> (BB 4.3). It enumerates every
lifecycle code the feed publishes — 0 not started, 1 live, 4 closed,
5 cancelled — each with a localized description, so a client builds its
status-name lookup entirely from this catalogue:
id values are exactly those carried by <sport_event_status status> on the feed (see Sport-event status),
so the catalogue and the live messages never disagree. descriptions/ru/match_status
returns the same ids with Russian descriptions.
The dictionary lists only the codes the feed currently publishes, so for
CS2 it is exactly {0, 1, 4, 5}. The extended UOF lifecycle codes a
football feed needs (2 suspended, 3 ended, 6 delayed, 7 interrupted,
8 postponed, 9 abandoned) are added to this same catalogue when that sport
ships — it always mirrors what is actually emitted.
GET descriptions/:locale/void_reasons decodes the numeric void_reason_id
each <bet_cancel> market carries into a stable id → localized signature
(BB 5.2), so an operator or bettor sees why a market was voided rather than
an opaque integer:
bet_cancel body is unchanged — this is purely the decode catalog. Today
the feed emits void_reason_id="4"; the other ids are part of the stable
vocabulary. descriptions/ru/void_reasons returns the same ids in Russian.
Identifiers
- Sport events accept
od:match:{number}or the Ticktock match UUID. - Tournament, competitor, and player paths accept a numeric HLTV ID or a URN whose final segment is that numeric ID.
- URNs may be URL encoded.
- Responses preserve an existing
od:match:*identifier when the match is linked to UOF. Ticktock-owned catalogue entities (tournaments, competitors, players) use stablett:*URNs. - The sport/category id is the bare UOF integer (
3for CS2), both in thesportscatalog and where<sport>is nested under a tournament — it maps to the client’scategory_id, so it is not URN-wrapped.
Schedules
Prematch schedule pagination uses offset semantics:
The prematch endpoint returns booked
discovered and upcoming events. The
live endpoint returns only booked live events. Per-tenant unbook overrides are
applied to both.
Prematch events are ordered by scheduled start time and then event ID. Read the
complete catalogue by requesting limit=1000, increasing start by the same
limit after every full page, and stopping when a page contains fewer than
limit events. The live schedule is intentionally not paginated.
ru:
Producer discovery
CallGET descriptions/producers before starting producer monitoring or
recovery. The response contains at least the configured live producer:
id is the exact value carried by the product attribute on
<alive>, odds, settlement, cancellation, recovery acknowledgement, and
<snapshot_complete> messages. The leading recovery path segment accepts the
producer id, scope (live), or name (ticktock_live). Deployments
overriding AMQP_PRODUCT_ID publish the configured value consistently in both
the catalog and messages.
The catalog lists one <producer> per stream the feed publishes. Today the
live-only volume needs just the live producer. If a separate stream is added —
for example a prematch producer — it appears here as an additional <producer>
with its own id and scope (e.g. scope="prematch"), and the feed publishes
that stream’s <alive> / odds with product equal to its id; its id /
scope / name are then also accepted in the :product path segment. A client
that runs recovery in scope phases (non-live first, then live) keys off the
scope value here.
Recovery paths
Ticktock currently exposes the live producer. Discover its ID and scope fromGET descriptions/producers, then use either value in :product;
unsupported products return HTTP 422.
The global recovery endpoint is:
product as a query parameter. Ticktock accepts and
ignores that duplicate, so both forms are valid:
Full snapshot mode publishes current active odds for every booked visible
match and terminates with
<snapshot_complete>. It deliberately does not scan
settlement/cancel history and is not constrained by the stateful recovery
window.
For incremental recovery, a positive after replays current active odds plus
settlement/cancel transitions after that timestamp. It must not be in the
future or older than the producer’s
stateful_recovery_window_in_minutes. The default is 4320 minutes (3 days),
but this is a deployment configuration value rather than a hard-coded platform
maximum. Requests outside the configured window return HTTP 422.
Every replay envelope carries the matching root request_id; the AMQP
recovery=1 header is supplementary. Event odds recovery republishes active
odds only for the selected event.
Event stateful recovery republishes settlements and cancellations only for the
selected event. Event requests accept optional request_id and node_id.
When request_id is omitted on an event request, Ticktock generates a positive
numeric correlation ID and returns it in the acknowledgement.
All three endpoints return HTTP 202; replay data arrives through the tenant’s
AMQP vhost and terminates with <snapshot_complete>.
A replayed <bet_settlement> / <bet_cancel> carries the original
transition timestamp — the time the market was settled or voided — not the
time of the replay. So a timestamp-keyed dedup matches the live message it
already processed and never re-settles; replaying the same window twice is
idempotent. Markets that transitioned together (one settlement batch) share a
timestamp and so a single envelope, exactly as in the live feed.
A global replay reconstructs the full state a warm-restart client would
otherwise rebuild from REST, in this order (then <snapshot_complete>):
<fixture_change>for each match whose lifecycle changed sinceafter(a kickoff →change_type="1", a cancellation →change_type="3"). A finish is not a fixture change — it rides<sport_event_status status="4">.<odds_change>per match — its current active markets.<bet_settlement>/<bet_cancel>for transitions sinceafter.<rollback_bet_settlement>/<rollback_bet_cancel>for any settlement or void reversed sinceafter, so a rollback during the gap is not lost.
timestamp, so the same dedup applies.
Canonical API
The compatibility facade is additive. Existing/cs2/v1/* and sport-agnostic
/v1/health, /v1/alive, /v1/whoami, and /v1/sports routes remain
available without contract changes.