Skip to main content
The B2B API exposes an UOF-compatible REST facade for integrations whose request classes resolve fixed UOF resource paths against a configured base URL. The facade uses the same CS2 data, tenant permissions, market catalogue, and AMQP recovery worker as the canonical /cs2/v1/* API. Use a base URL ending in /v1/, for example:
Set Accept: application/xml on every consumed request. The canonical Ticktock API honors content negotiation: Accept: application/json, */*, or an absent Accept header selects JSON on dual-format endpoints. Unlike the upstream UOF API, Ticktock does not deliberately ignore a JSON Accept header. The UOF compatibility paths documented below return their hand-tuned XML response, and item 8.8 tracks complete UOF XML coverage for every consumed catalog/detail path.

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 either X-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. Only en 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:
Every successful compatibility response is XML with 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>
A client that parses every body as XML detects not-found from the in-body marker on every catalogue/detail endpoint — none return a JSON {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 publishes0 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:
The integer 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:
The 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 stable tt:* URNs.
  • The sport/category id is the bare UOF integer (3 for CS2), both in the sports catalog and where <sport> is nested under a tournament — it maps to the client’s category_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.
Clients configured with both supported locales should repeat catalogue and detail requests with ru:

Producer discovery

Call GET descriptions/producers before starting producer monitoring or recovery. The response contains at least the configured live producer:
The 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 from GET descriptions/producers, then use either value in :product; unsupported products return HTTP 422. The global recovery endpoint is:
Some clients repeat product as a query parameter. Ticktock accepts and ignores that duplicate, so both forms are valid:
Global recovery keeps the canonical recovery parameters: 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>):
  1. <fixture_change> for each match whose lifecycle changed since after (a kickoff → change_type="1", a cancellation → change_type="3"). A finish is not a fixture change — it rides <sport_event_status status="4">.
  2. <odds_change> per match — its current active markets.
  3. <bet_settlement> / <bet_cancel> for transitions since after.
  4. <rollback_bet_settlement> / <rollback_bet_cancel> for any settlement or void reversed since after, so a rollback during the gap is not lost.
Every replayed envelope — including the fixture change and the rollbacks — reuses the original transition 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.
Last modified on June 21, 2026