Rankacy Sportsbook B2B API
Read-only HTTP feed of CS2 bot-league matches, markets, live odds and stream metadata for sportsbook and bookmaker partners.
/openapi.json on every running instance.
Core Concepts
| Concept | Description |
|---|---|
| Match | One bot-vs-bot CS2 game on a specific map. States: PREGAME, WARMUP, LIVE, FINISHED. |
| Round | One in-match round, tracked via current_round_number. |
| Market | A betting market type: MATCH (full match winner), ROUND (current round), NEXT_ROUND. |
| Sides | CT / T refer to the server role. DRAW is only available on the MATCH market. Team IDs map sides to actual teams. |
| Decimal odds | Always returned as strings (e.g. "1.87"). No fractional or American formats. |
| Timestamps | ISO-8601 in UTC throughout. |
Authentication
Every request must include an API key via the X-API-Key HTTP header:
X-API-Key: rksb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are issued by a Rankacy administrator via the admin panel. The raw key is shown once at creation — if lost, request a key rotation.
- Missing or invalid keys return
401 Unauthorized. - Deactivated partners receive
401even with a valid key. - All requests are audit-logged server-side (method, path, status, duration).
curl -H "X-API-Key: $RANKACY_API_KEY" \
https://YOUR-HOST/api/b2b/ping
{
"status": "ok",
"partner_id": 3,
"partner_name": "Fortuna CZ",
"server_time": "2026-04-15T10:22:03Z"
}
Base URL
All endpoints are served under:
/api/b2b/*
Replace YOUR-HOST in examples with the hostname provided during onboarding.
Error Handling
All errors use a standard JSON envelope:
{ "detail": "Match not found" }
| Status | Meaning |
|---|---|
401 | Missing / invalid API key, or partner deactivated |
404 | Unknown match, season, or webhook ID |
422 | Invalid query parameters (bad enum value, out-of-range, etc.) |
5xx | Server error — safe to retry after a short backoff |
Endpoints — Core
Validates your API key and returns your partner identity with the current server time.
Response
"ok"Returns the three supported betting markets. This data is static — safe to cache indefinitely.
Response — Array of:
MATCH | ROUND | NEXT_ROUND["CT","T","DRAW"]Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
status | enum | — | PREGAME, WARMUP, LIVE, FINISHED |
season_id | integer | — | Filter to one season/tournament |
updated_since | datetime | — | Only matches updated after this instant |
limit | integer | 20 | 1–100 |
offset | integer | 0 | Pagination offset |
Response
MatchSummary Schema
de_dust2)PREGAME | WARMUP | LIVE | FINISHED{ id, name, logo_name }{ id, name, logo_name }curl -H "X-API-Key: $KEY" \
"https://HOST/api/b2b/matches?status=LIVE&limit=5"
Returns queued season matches that haven't started yet.
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
season_id | integer | — | Filter to one season |
limit | integer | 20 | 1–100 |
offset | integer | 0 | Pagination offset |
Response
UpcomingMatchSummary Schema
{ id, name, logo_name }{ id, name, logo_name }Returns all MatchSummary fields plus match-page specific context. Returns 404 if the match does not exist.
Path Parameters
| Param | Type | Description |
|---|---|---|
match_id | required integer | Match ID |
Treat this endpoint as the source of truth for score, status and side mapping.
ct_team_id / t_team_id tell you which real team currently occupies the
CT / T side, while current_round_number tells you which round the
live markets refer to.
Additional Response Fields
match_idMatchRound Schema
MatchRoundEvent Schema
KILL or PLANT_STARTEDkill, plant, defuse, winner, utilitywebhook_events arrives for this match_id, refetch this endpoint.
Returns the latest odds for all three markets on a match. Each market reports is_available — a market may be unavailable between rounds when the model has no active prediction.
Path Parameters
| Param | Type | Description |
|---|---|---|
match_id | required integer | Match ID |
Response
MarketOdds Schema
MATCH | ROUND | NEXT_ROUNDMarketOddsSide Schema
CT, T, or DRAWnull for DRAW)"1.87"){
"match_id": 42,
"updated_at": "2026-04-15T10:22:03Z",
"markets": [
{
"market": "MATCH",
"round_number": null,
"is_available": true,
"margin": "0.05",
"updated_at": "2026-04-15T10:22:03Z",
"sides": [
{ "side": "CT", "team_id": 9, "decimal_odd": "1.87" },
{ "side": "T", "team_id": 10, "decimal_odd": "1.92" },
{ "side": "DRAW", "team_id": null, "decimal_odd": "18.00" }
]
},
{
"market": "ROUND",
"round_number": 7,
"is_available": true,
"margin": "0.04",
"updated_at": "2026-04-15T10:22:01Z",
"sides": [
{ "side": "CT", "team_id": 9, "decimal_odd": "2.10" },
{ "side": "T", "team_id": 10, "decimal_odd": "1.75" }
]
}
]
}
Returns the time-series of odds snapshots for a specific market on a match.
Path Parameters
| Param | Type | Description |
|---|---|---|
match_id | required integer | Match ID |
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
market | required enum | — | MATCH, ROUND, or NEXT_ROUND |
round_number | integer | — | Required for ROUND / NEXT_ROUND |
Response
updated_atOddsHistoryPoint Schema
CT, T, or DRAWReturns every market snapshot written after the supplied since timestamp.
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
since | datetime | — | Omit on first call. Use previous server_time for subsequent calls. |
limit | integer | 200 | 1–1000 |
Response
since value you sentsince in your next requestOddsChange Schema
MATCH | ROUND | NEXT_ROUND{
"since": "2026-04-15T10:22:00Z",
"server_time": "2026-04-15T10:22:03.421Z",
"webhook_events": ["ODDS_UPDATED"],
"changes": [
{
"match_id": 42,
"match_status": "LIVE",
"market": "MATCH",
"round_number": null,
"updated_at": "2026-04-15T10:22:03Z",
"is_available": true,
"sides": [
{ "side": "CT", "team_id": 9, "decimal_odd": "1.87" },
{ "side": "T", "team_id": 10, "decimal_odd": "1.92" },
{ "side": "DRAW", "team_id": null, "decimal_odd": "18.00" }
]
}
]
}
Returns the Twitch channel and embed URL for displaying the live stream on your site. The parent query parameter is derived from your request hostname.
Response
src URLSeasons
Returns all seasons/tournaments.
Response — Array of:
Returns ranked standings for a season, ordered by points.
Path Parameters
| Param | Type | Description |
|---|---|---|
season_id | required integer | Season ID |
Response
StandingRow Schema
Webhooks
Register HTTPS endpoints to receive real-time push notifications. Each delivery is signed with HMAC-SHA256 so you can verify authenticity server-side.
Webhooks are lightweight invalidation messages, not full REST payloads. Use the incoming
match_id plus each endpoint's webhook_events field to decide which B2B
endpoint to refetch.
Supported Event Types
| Event | Description |
|---|---|
MATCH_CREATED | A new match has been created |
MATCH_STARTED | A match transitioned to LIVE |
MATCH_FINISHED | A match reached final state |
ODDS_UPDATED | Odds changed for any market on a match |
Delivery Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-Rankacy-Event | One of the event types above |
X-Rankacy-Signature | sha256=<hex> — HMAC-SHA256 of the request body |
/odds/changes poll feed as your source of truth.
Response — Array of:
Request Body
Response (201 Created)
true on creationcurl -X POST -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"event_type":"ODDS_UPDATED","target_url":"https://example.com/hooks/rankacy"}' \
https://HOST/api/b2b/webhooks
Path Parameters
| Param | Type | Description |
|---|---|---|
webhook_id | required integer | Webhook ID to remove |
Response
{ "message": "Webhook removed" }
Returns 404 if the webhook doesn't exist or doesn't belong to you.
Verifying Webhook Signatures
Every webhook delivery is signed. Verify the X-Rankacy-Signature header before processing.
import hmac, hashlib
def verify_signature(body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, header)
const crypto = require("crypto");
function verifySignature(body, header, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(header)
);
}
Integration Flow
- Bootstrap — Call
GET /marketsandGET /seasonsto populate your static UI data. Cache these. - Render lobby —
GET /matches?status=LIVEfor active matches, plusGET /matches/{id}for detail. - Push updates — If you register webhooks, use the endpoint-level
webhook_eventsarray as your invalidation map and refetch only the affected endpoint when an event arrives. - Pull fallback — If you do not register webhooks, fetch once on page load and refresh only on user navigation. For live odds polling, use
GET /odds/changes. - Match page — Use
GET /streamfor the Twitch iframe. Show all three markets side-by-side. TreatGET /matches/{id}as the source of truth for score, status and settlement. - Settlement — A match is settled when
status == "FINISHED". Final scores are on the match detail response.
Polling Recipe
- First call:
GET /api/b2b/odds/changes(nosinceparam). - Store
server_timefrom the response. - Every 1–2 seconds:
GET /api/b2b/odds/changes?since=<server_time> - Apply changes to your local cache, keyed by
(match_id, market, round_number). - Update
server_timefrom each new response.
import time, requests
API = "https://HOST/api/b2b"
HEADERS = {"X-API-Key": "rksb_live_..."}
since = None
while True:
params = {"limit": 500}
if since:
params["since"] = since
r = requests.get(f"{API}/odds/changes", headers=HEADERS, params=params)
data = r.json()
since = data["server_time"]
for change in data["changes"]:
key = (change["match_id"], change["market"], change["round_number"])
# update your local cache with change["sides"], change["is_available"]
time.sleep(1.5)
Versioning & Change Policy
- The API surface under
/api/b2b/*is considered stable. - Additive changes (new optional fields, new endpoints) ship without notice.
- Breaking changes will be released under a new prefix (
/api/b2b/v2/...) with a transition period announced per partner.