Public REST API reference
Every Braket arena exposes a public, read-only REST API that mirrors the data on its public site. Use it to build Discord bots, stream overlays, standings widgets, or in-game "current tournaments" panels. This chapter documents the base URL, authentication, rate limits, and every endpoint with its parameters, an example request, an example response, and its status codes. It is accurate to src/web/routes/publicApi.ts.
Write access (submitting results) is not part of this API; it is the Steam-ticket-authenticated game-client protocol in The game-client result protocol (dual attestation). The same chapter also specifies the game-client recording and near-live segment endpoints (/api/v1/matches/:id/recording/init, /api/v1/recordings/…), which are Steam-ticket-authenticated as well and are not duplicated here. Future write scopes and webhooks are on the roadmap.
Base URL and tenancy
Each arena's API lives on its own host; the tenant is the subdomain:
https://<yourgame>.braket.gg/api/v1
or your arena's custom domain. Everything returned is scoped to that tenant; there is no cross-arena endpoint here.
All responses are application/json, versioned under /api/v1, with stable field names. All timestamps are ISO-8601 UTC strings. Players are identified by SteamID64. New fields may be added within v1, so be tolerant of extra fields.
Authentication and rate limits
Read endpoints are public and rate-limited to 60 requests per minute per IP. Sending a valid API key raises the limit to 600 per minute and prepares you for future private/write scopes:
Authorization: Bearer bk_<prefix>_<secret>
The key format is bk_<8 hex chars>_<secret>; the header must match ^Bearer (bk_[0-9a-f]{8}_[A-Za-z0-9_-]+)$. A key is bound to the tenant it was minted for and is ignored on any other host (the server checks the key's tenant equals the host's tenant). An unrecognized or wrong-tenant key is simply treated as anonymous (you get the 60/min limit), not rejected.
Mint keys in Admin, Site settings, API keys. The full key is shown once at creation and stored only as a SHA-256 hash. Revoke any time.
curl -H "Authorization: Bearer bk_1a2b3c4d_your_secret_here" \
https://summergame.braket.gg/api/v1
Errors
Errors are an HTTP status plus a JSON body { "error": "<code>" }. Codes you can see: tournament_not_found, player_not_found, invalid_steam_id.
GET /api/v1
Meta / health. Confirms which tenant you are addressing and whether your key authenticated.
Parameters: none.
curl https://summergame.braket.gg/api/v1
{
"api": "braket",
"version": "1",
"tenant": { "slug": "Summer Game", "name": "Summer Game" },
"authenticated": true,
"endpoints": [
"GET /api/v1/tournaments",
"GET /api/v1/tournaments/:slug",
"GET /api/v1/ladder?season=",
"GET /api/v1/players/:steamId"
]
}
| Field | Type | Meaning |
|---|---|---|
api |
string | Always "braket" |
version |
string | API version, "1" |
tenant |
object | The arena (slug, name) |
authenticated |
boolean | Whether a valid API key for this tenant was presented |
endpoints |
string[] | The available endpoints |
Status: 200.
GET /api/v1/tournaments
All non-draft tournaments (open, running, and past). Draft tournaments are never returned.
Parameters: none.
curl https://summergame.braket.gg/api/v1/tournaments
{
"tournaments": [
{
"slug": "summer-cup",
"name": "Summer Cup",
"description": "Our flagship seasonal open.",
"status": "registration_open",
"format": "groups_playoffs",
"registrationOpensAt": "2026-06-01T00:00:00Z",
"registrationClosesAt": "2026-06-20T00:00:00Z",
"startsAt": "2026-06-22T18:00:00Z",
"maxPlayers": 32
}
]
}
Each tournament summary:
| Field | Type | Meaning |
|---|---|---|
slug |
string | Tournament slug (use in the :slug endpoint) |
name |
string | Display name |
description |
string | Description |
status |
string | One of registration_open, registration_closed, group_stage, playoffs, completed, cancelled (never draft) |
format |
string | groups_playoffs or playoffs_only |
registrationOpensAt / registrationClosesAt |
string | Registration window |
startsAt |
string | Group stage start (groupStageStartsAt) |
maxPlayers |
number | Registration cap |
Status: 200.
GET /api/v1/tournaments/:slug
A full tournament: summary fields plus players, group standings, playoff bracket, and podium.
| Path param | Type | Meaning |
|---|---|---|
slug |
string | The tournament slug |
curl https://summergame.braket.gg/api/v1/tournaments/summer-cup
{
"slug": "summer-cup",
"name": "Summer Cup",
"description": "Our flagship seasonal open.",
"status": "playoffs",
"format": "groups_playoffs",
"registrationOpensAt": "2026-06-01T00:00:00Z",
"registrationClosesAt": "2026-06-20T00:00:00Z",
"startsAt": "2026-06-22T18:00:00Z",
"maxPlayers": 32,
"playerCount": 16,
"players": [
{ "steamId": "76561197977425772", "personaName": "Kosmo", "avatarUrl": "https://…" }
],
"groups": [
{
"name": "Group A",
"standings": [
{ "rank": 1, "player": { "steamId": "…", "personaName": "…", "avatarUrl": "…" },
"played": 3, "wins": 3, "losses": 0, "scoreDiff": 6, "points": 9 }
],
"matches": [ { "id": 12, "stage": "group", "round": "Round 1", "bracketSlot": null,
"status": "confirmed", "opensAt": "…", "closesAt": "…",
"player1": { "…": "…" }, "player2": { "…": "…" }, "winner": { "…": "…" },
"score": { "p1": 2, "p2": 0 }, "resultSource": "player_report" } ]
}
],
"bracket": [
{ "label": "Quarterfinals", "side": "left",
"matches": [ { "id": 42, "stage": "playoff", "round": "Quarterfinal 1",
"bracketSlot": "R1M1", "status": "confirmed", "opensAt": "…", "closesAt": "…",
"player1": {"…":"…"}, "player2": {"…":"…"}, "winner": {"…":"…"},
"score": { "p1": 2, "p2": 1 }, "resultSource": "game_client" } ] }
],
"podium": [ { "rank": 1, "player": { "steamId": "…", "personaName": "…", "avatarUrl": "…" } } ]
}
Top-level shape:
| Field | Type | Meaning |
|---|---|---|
| (summary fields) | Same as the list endpoint | |
playerCount |
number | Number of registrations |
players |
array | Registered players (steamId, personaName, avatarUrl) |
groups |
array | Group stage: each { name, standings[], matches[] } |
bracket |
array | Playoff columns: each { label, side, matches[] } |
podium |
array | Resolved podium: each { rank, player } |
A standings row: { rank, player, played, wins, losses, scoreDiff, points }.
A bracket column: label is the round name, side is "left"/"right"/center placement in the bracket layout.
The match object (used in both groups[].matches and bracket[].matches):
| Field | Type | Meaning |
|---|---|---|
id |
number | Match id |
stage |
string | group, playoff, small_final, or final |
round |
string | Human round label (roundLabel) |
bracketSlot |
string or null | Bracket position (R1M3, SF1, SMALL_FINAL, FINAL) |
status |
string | scheduled, live, awaiting_result, confirmed, disputed, forfeited |
opensAt / closesAt |
string | Match window |
player1 / player2 |
object or null | The two sides (null when undecided) |
winner |
object or null | The winner (null until resolved) |
score |
object or null | { p1, p2 } when both scores are set, else null |
resultSource |
string or null | player_report, admin, game_client, ai_video, or bye |
A player object is always { steamId, personaName, avatarUrl } or null.
Status: 200; 404 { "error": "tournament_not_found" } if the slug is unknown or the tournament is a draft.
GET /api/v1/ladder
The season ladder for this arena.
| Query param | Type | Default | Meaning |
|---|---|---|---|
season |
string | latest season | A season slug (for example 2026-q3) or all; anything else falls back to the latest season |
curl "https://summergame.braket.gg/api/v1/ladder?season=2026-q3"
{
"season": "2026-q3",
"seasons": ["2026-q3"],
"ladder": [
{ "rank": 1, "player": { "steamId": "…", "personaName": "…", "avatarUrl": "…" },
"points": 320, "tournaments": 4 }
]
}
| Field | Type | Meaning |
|---|---|---|
season |
string | The season actually returned |
seasons |
string[] | All available season slugs |
ladder |
array | Rows of { rank, player, points, tournaments } where tournaments is how many completed tournaments the player counted toward the season |
Status: 200.
GET /api/v1/players/:steamId
A player's profile and stats for this arena.
| Path param | Type | Meaning |
|---|---|---|
steamId |
string | A SteamID64; must match ^\d{17}$ |
curl https://summergame.braket.gg/api/v1/players/76561197977425772
{
"player": { "steamId": "76561197977425772", "personaName": "Kosmo", "avatarUrl": "https://…" },
"stats": {
"matchesPlayed": 12,
"wins": 8,
"losses": 4,
"winRate": 67,
"trophies": [ { "rank": 1, "tournamentId": 3 } ]
}
}
| Field | Type | Meaning |
|---|---|---|
player |
object | { steamId, personaName, avatarUrl } |
stats.matchesPlayed |
number | Resolved matches (confirmed or forfeited) the player was in |
stats.wins |
number | Wins among those |
stats.losses |
number | matchesPlayed - wins |
stats.winRate |
number | Integer percentage, round(wins / matchesPlayed * 100), or 0 when none played |
stats.trophies |
array | Trophy grants: { rank, tournamentId } |
Status: 200; 400 { "error": "invalid_steam_id" } if steamId is not 17 digits; 404 { "error": "player_not_found" } if no such player in this arena.
Quick client examples
// Latest ladder top 3
const r = await fetch('https://summergame.braket.gg/api/v1/ladder');
const { ladder } = await r.json();
console.log(ladder.slice(0, 3));
// A tournament's podium
const t = await fetch('https://summergame.braket.gg/api/v1/tournaments/summer-cup');
const { podium } = await t.json();