Home Β· Events

πŸ“š API Documentation

Public API reference for xb.live / Insignia Stats. Most routes are anonymous; a few require a valid Insignia session (noted per endpoint).

Table of Contents

Base URL

All endpoints are relative to your site (e.g. https://insigniastats.live).

Note: For game endpoints you can use either title ID (hex, e.g. 4d530064) or game name (URL-encoded where used in paths). Title IDs are stable identifiers across the platform.

Authentication (Insignia Auth)

Insignia Stats uses a separate auth service for login. To sign in on the website, use the Login page. Your account and password are managed by the Insignia auth service; the stats site does not store your password.

InsigniaAuth SDK

For integrating login into your own pages or apps, include the client SDK and use the InsigniaAuth class. The SDK persists the session in localStorage and exposes login, logout, and user data methods.

Include the script:
<script src="https://insigniastats.live/insignia-auth.js"></script>
Constructor:
Option Description
apiUrlAuth API base URL (default: https://auth.insigniastats.live/api)
storageKeylocalStorage key for session (default: insignia_auth)
Methods:
MethodDescription
login(email, password)Log in; returns { success, user, sessionKey }. Emits login event.
logout()Log out and clear session. Emits logout event.
getUser()Returns current user { username, email } or null.
isLoggedIn()Returns true if a session key exists (does not verify server-side).
verifySession()Verifies the session with the auth server; returns boolean.
getFriends()Returns cached friends: { friends: [{ gamertag, status, isOnline, game?, duration?, lastSeen? }], lastUpdated, count }. When online: game, duration; when offline: lastSeen.
getGames()Returns cached My Games Played: { games: [{ title, lastPlayed, iconUrl }], lastUpdated, count }. lastPlayed is e.g. "10 minutes ago".
getProfile()Returns cached profile: { isOnline, status, game?, timeOnline?, gamesPlayed: [{ title, lastPlayed, iconUrl }], lastUpdated, count }.
refreshFriends()Refreshes friends from Insignia; returns updated friends data.
refreshGames()Refreshes games (from profile My Games Played); returns updated games data.
refreshProfile()Refreshes profile from Insignia; returns updated profile.
getMutes()Deprecated. Mutes no longer available from Insignia; returns empty list.
startAutoVerify(intervalMs)Periodically re-verifies the session (e.g. every 5 minutes).
Events: on('login', fn), on('logout', fn), on('error', fn).
Example:
const auth = new InsigniaAuth({ apiUrl: 'https://auth.insigniastats.live/api' });
if (auth.isLoggedIn()) {
  const user = await auth.getUser();
  console.log('Logged in as', user.username);
} else {
  await auth.login(email, password);
}

Public profiles

Per-user profile data for the site. :username is the Insignia site username (URL-encode when needed). These GET routes are public.

GET
/api/profile/:username
Full profile bundle for the dashboard / profile page: display fields, play-time summary, achievement score and count, recent new unlocks, leaderboard presence, and linked gamertag flags. The public display name is always the site username in the response root (there is no separate nickname).
Response (shape):
{
  "username": "PlayerOne",
  "profile": {
    "avatar_url": "...",
    "discord_profile_url": "...",
    "linked_gamertag": "...",
    "xbox_verified": false,
    "bio": "...",
    "social_links": []
  },
  "playTime": { "totalMinutes": 0, "lastState": "offline", "currentGame": null, ... },
  "achievementScore": 1200,
  "achievementCount": 5,
  "newUnlocks": [],
  "leaderboardPlayerName": "GamertagOrUsername",
  "leaderboardStats": { "leaderboardsOn": 0, "top10Count": 0 },
  "usesExplicitXboxLiveGamertag": false
}
GET
/api/profile/:username/achievements
Evaluates achievements for the user, then returns score, unlocked achievements with details, and grouped layout for the UI. Hidden achievements show placeholder name/description until unlocked; after unlock, the group list uses the revealed title and description.
Response:
{
  "score": 1200,
  "achievements": [ ... ],
  "groups": [ ... ]
}
GET
/api/profile/:username/games-played
Games played summary (play-time and/or leaderboard-derived rows).
Response:
{
  "games": [ ... ],
  "leaderboardSupplement": false,
  "hasPlayTimeTracking": true
}
GET
/api/profile/:username/top-scores
Top leaderboard entries for the user’s resolved gamertag (linked Xbox gamertag when set, otherwise site username).
Query Parameters:
ParameterTypeDefaultDescription
limitinteger10Max entries (1–50)
Response:
{
  "playerName": "...",
  "explicitXboxLiveGamertag": null,
  "usingSiteUsernameFallback": true,
  "scores": [ ... ]
}

Update your profile (session)

Changing bio, avatar, linked gamertag, etc. requires a valid Insignia session. Send X-Session-Key as a header, or sessionKey in the JSON body.

PATCH
/api/me/profile
Updates the signed-in user’s profile. Writable fields: avatar_url, avatar_hue, discord_profile_url, linked_gamertag, bio, sync_avatar_from_discord. Optional social_links (normalized list) when Discord is linked per server rules. Discord URL changes may trigger avatar/name sync from Discord. You cannot set a separate public display name; it is always your Insignia site username.
Auth / errors: 400 β€” missing sessionKey, invalid social links, or bad Discord URL; 401 β€” invalid session; 403 β€” social links without verified Discord; 502 β€” Discord lookup failure in some cases.

Reputation (player feedback)

Star ratings and optional comments left on user profiles. The displayed average uses a Bayesian-style prior (equivalent to 10 ratings at 5β˜…) so new profiles stay near 5 until real feedback arrives.

GET
/api/profile/:username/reputation
Public reputation stats and recent feedback. If the caller is signed in and has rated this profile, viewer contains their stars/comment (omit X-Session-Key for anonymous).
Query Parameters:
ParameterTypeDefaultDescription
feedbackLimitinteger12Recent feedback rows (max 30)

Optional session: header X-Session-Key or query sessionKey β€” used only to populate viewer when the signed-in user has already submitted feedback.

Response:
{
  "average": 4.85,
  "ratingCount": 3,
  "effectiveVotes": 13,
  "priorOnly": false,
  "feedback": [
    { "raterUsername": "...", "stars": 5, "comment": null, "createdAt": "..." }
  ],
  "viewer": null
}

When signed in and you have rated this profile, viewer is an object with stars, comment, createdAt; otherwise null.

POST
/api/profile/:username/reputation
Submit or update your rating for another user. You cannot rate yourself.
Body (JSON):
FieldTypeDescription
sessionKeystringRequired unless using X-Session-Key header
starsinteger1–5
commentstringOptional; trimmed, max 500 characters
Errors: 400 β€” missing session key, invalid username, self-rating, or invalid stars; 401 β€” invalid session.
Response:
{
  "success": true,
  "reputation": { "average": 4.9, "ratingCount": 4, ... },
  "viewer": { "stars": 5, "comment": null, "createdAt": "..." }
}

Gamerscore

Site-wide achievement catalog and leaderboards (points total and recent unlocks).

GET
/api/achievements/catalog
Full list of achievements plus a grouped layout for the UI. Some entries may include is_hidden; the public catalog never exposes revealed_name / revealed_description (those appear only on a profile after the user unlocks the achievement).
Response:
{
  "achievements": [ ... ],
  "groups": [ ... ],
  "stats": {
    "maxGamerscore": 12345,
    "totalAchievements": 400,
    "playersWithAchievements": 1200
  }
}
GET
/api/achievements/leaderboard
Top users by total gamerscore.
Query Parameters:
ParameterTypeDefaultDescription
limitinteger25Max rows (1–100)
Response:
{ "leaderboard": [ { "rank": 1, "username": "...", "totalScore": 1200, "achievementCount": 5 } ] }
GET
/api/achievements/recent
Most recent achievement unlocks across the site. Each achievementName includes the game label (e.g. Insignia Tourist β€” XBL Core or Be a WORM β€” Worms 4: Mayhem). Hidden achievements use the title Hidden Achievement with no spoiler details (no spoiler).
Query Parameters:
ParameterTypeDefaultDescription
limitinteger10Max rows (1–100)
Response:
{ "recent": [ { "username": "...", "unlockedAt": "...", "achievementId": 1, "achievementKey": "...", "achievementName": "...", "gameName": "...", "points": 10 } ] }

Insignia Stats

Site-wide stats scraped from the Insignia.live homepage.

GET
/api/insignia-stats/daily-registered
Daily snapshot of total registered users on Insignia (for charts).
Query Parameters:
Parameter Type Default Description
days integer 30 Number of days of history (1–365)
Response:
[
  { "date": "2025-02-01", "registered_count": 30028 },
  { "date": "2025-02-02", "registered_count": 30105 }
]
GET
/api/insignia-stats/online-24h
Five-minute snapshots of "Users Online Now" from the Insignia.live homepage. Data is retained for 30 days.
Query Parameters:
Parameter Type Default Description
days integer 1 Number of days of history: 1 (last 24 hours) or 7 (last 7 days), up to 30
Response:
[
  { "recorded_at": "2025-02-13 12:00:00", "online_count": 48 },
  { "recorded_at": "2025-02-13 12:05:00", "online_count": 52 }
]
GET
/api/insignia-stats/lobbies-24h
Five-minute snapshots of total lobby count across all games (from match data). Data is retained for 30 days.
Query Parameters:
Parameter Type Default Description
days integer 1 Number of days of history: 1 (last 24 hours) or 7 (last 7 days), up to 30
Response:
[
  { "recorded_at": "2025-02-13 12:00:00", "lobby_count": 12 },
  { "recorded_at": "2025-02-13 12:05:00", "lobby_count": 14 }
]

Online & site activity

Live per-game online counts, historical series, and a compact summary for β€œall stats” style widgets.

GET
/api/stats/summary
Aggregated peaks and activity for the last 24 hours or last 7 days (homepage / charts).
Query Parameters:
ParameterTypeDefaultDescription
periodstringtodaytoday β†’ last 24h; 7d β†’ last 168h
Response:
{
  "period": "today",
  "peakOnline": 48,
  "peakOnlineAt": "...",
  "peakLobbies": 12,
  "peakLobbiesAt": "...",
  "mostPlayedGame": { "name": "...", "titleId": "...", "peak": 42 },
  "gamesWithActivityCount": 15,
  "totalSnapshotCount": 288
}
GET
/api/online-users
Current online user counts for all tracked games.
Response:
{
  "Game Name One": {
    "name": "Game Name One",
    "online": 42,
    "titleId": "4d53006e",
    "image": "https://...",
    "publisher": "Microsoft Game Studios"
  },
  "Game Name Two": { ... }
}
GET
/api/title/:titleId/online-users/history
Historical online user data for a game by title ID.
URL Parameters:
Parameter Type Description
titleId string Game title ID (e.g. 4d530064)
Query Parameters:
Parameter Type Default Description
hours integer 48 Hours of history to retrieve
Example:
GET /api/title/4d530064/online-users/history?hours=24
GET
/api/online-users/history/:gameName
Historical online user data for a game by name. gameName is URL-encoded (e.g. Halo%202).

Game Data

GET
/api/title/:titleId
Game metadata by title ID.
Response:
{
  "titleId": "4d530064",
  "name": "Halo 2",
  "title": "Halo 2",
  "image": "https://...",
  "publisher": "Microsoft Game Studios",
  "releaseDate": "2004-11-09"
}
GET
/api/game/:gameName
Game metadata by game name. gameName is URL-encoded.

Game Statistics

GET
/api/title/:titleId/stats
Aggregate leaderboard statistics for a game by title ID.
Response:
{
  "titleId": "4d530064",
  "gameName": "Halo 2",
  "leaderboard_count": 120,
  "total_entries": 500000,
  "time_entries": 300000,
  "score_entries": 200000
}
GET
/api/game/:gameName/stats
Aggregate leaderboard statistics for a game by name.
GET
/api/title/:titleId/daily-stats
Daily stats (lobby count, peak players, etc.) for a game by title ID.

Leaderboards

GET
/api/title/:titleId/leaderboards
List of all leaderboards for a game by title ID.
Response:
[
  {
    "id": 42,
    "name": "Leaderboard Name",
    "titleId": "4d530064",
    "gameName": "Halo 2"
  }
]
GET
/api/game/:gameName/leaderboards
List of all leaderboards for a game by name.
GET
/api/title/:titleId/leaderboard/:id
All entries for a specific leaderboard (by title ID and leaderboard ID).
Response:
{
  "titleId": "4d530064",
  "gameName": "Halo 2",
  "leaderboardId": "42",
  "name": "Leaderboard Name",
  "entries": [
    {
      "rank": "1",
      "name": "PlayerName",
      "rating": "1000000",
      "timeMs": 120000,
      "timeFormatted": "2:00.000",
      "isTime": true
    }
  ]
}
GET
/api/game/:gameName/leaderboard/:id
All entries for a specific leaderboard (by game name and leaderboard ID).

Latest Changes

Recent leaderboard entry changes (new, updated, or removed).

GET
/api/changes
Latest changes across all games.
Query Parameters:
Parameter Type Description
limit integer Max number of changes to return
offset integer Offset for pagination
game string Filter by game name
GET
/api/changes/games
List of game names that have at least one change (for filter dropdowns).
Response:
["Halo 2", "Project Gotham Racing 2", ...]
GET
/api/title/:titleId/changes
Latest changes for a single game by title ID.
GET
/api/changes/by-date
Daily counts of leaderboard changes (for charts). Query: days (1–365, default 30).
GET
/api/changes/sync-runs
Per–sync-run change stats. Query: days (default 30), optional limit to cap rows.

Matches

GET
/api/title/:titleId/matches
Latest active matches for a game (e.g. last hour) by title ID.
Response:
{
  "titleId": "4d53004b",
  "gameName": "Project Gotham Racing 2",
  "matches": [
    {
      "hostName": "PlayerName",
      "currentPlayers": 8,
      "maxPlayers": 8,
      "timestamp": "2025-02-13 12:00:00"
    }
  ]
}
GET
/api/game/:titleId/matches
Live match notifications from the match database (richer fields than /api/title/:titleId/matches). Path uses the same hex title ID as other /api/title/... routes.
GET
/api/title/:titleId/matches/history
Match history for a game. Query: hours (default 24).
GET
/api/title/:titleId/matches/stats
Match statistics for a game (e.g. last 24 hours): total matches, peak players, unique hosts.
GET
/api/game/:gameName/matches
Legacy: latest matches by URL-encoded game name. Prefer /api/title/:titleId/matches or /api/game/:titleId/matches.
GET
/api/game/:gameName/matches/history
Legacy: match history by game name. Query: hours. Prefer /api/title/:titleId/matches/history.
GET
/api/game/:gameName/matches/stats
Legacy: match statistics by game name. Prefer /api/title/:titleId/matches/stats.

Events

GET
/api/events
List of upcoming and recent events.
GET
/api/events/:id
Single event by ID.
GET
/api/events/:id/summary
Public summary for an event (e.g. registration/payout aggregates when applicable).
GET
/api/paid-events
List of paid events.
GET
/api/paid-events/summary
Summary of paid events.

Homebrew Leaderboards (SDK)

Endpoints for homebrew games (e.g. the original Xbox "Test Game") to submit player scores tied to a player's Insignia account, and for the public to read the resulting leaderboards. These power the Homebrew Leaderboards page and the xb.live Homebrew SDK.

How verification works (why scores can't be faked)

The original Xbox runs open homebrew, so the API cannot simply trust a number. Instead a score submission carries the run's seed and a compact input log, and the server re-simulates the entire run with the identical deterministic engine. The score is accepted only if the replay reproduces it exactly. This is layered with:

LayerWhat it stops
HMAC-SHA256 app signatureRaw curl forgery β€” every run/start and score call is signed with the game's secret (issued on approval).
Single-use run nonce + server seedReplay/duplicate submissions and pre-baked runs. The seed is chosen by the server at run/start, so players can't shop for a lucky seed.
Deterministic replayFabricated scores β€” a number with no valid input log (or a log that doesn't reproduce it) is rejected.
Plausibility & rate limitsImpossible timings and submission floods.

Only replay-verified scores are ranked on the public board. See the SDK repo's docs/anti-cheat.md for the full design and its honest limitations.

Request signing (HMAC)

Approved games receive a game_secret. Build a lowercase-hex HMAC-SHA256 over a canonical string:

// run/start
sig = HMAC_SHA256(game_secret, "start|" + game_id)

// score   (input_hash = SHA256_hex(input_log))
msg = "score|" + game_id + "|" + run_id + "|" + nonce + "|" +
      board_id + "|" + score + "|" + seed + "|" + input_hash
sig = HMAC_SHA256(game_secret, msg)

// achievements   (input_hash = SHA256_hex(input_log))
msg = "ach|" + game_id + "|" + run_id + "|" + seed + "|" + input_hash
sig = HMAC_SHA256(game_secret, msg)

The player's session is supplied in the X-Session-Key header (from Insignia login).

POST
/api/hb/run/start
Opens a server-tracked run. Requires X-Session-Key and a valid app signature. Returns a single-use nonce and the seed the game must play.
Body:
{ "game_id": "og-testgame", "sig": "<hmac hex>" }
Response:
{
  "success": true,
  "run_id": "<hex>",
  "nonce": "<hex>",
  "seed": 123456789,
  "server_time": 1733250000,
  "ttl": 1800
}
POST
/api/hb/score
Submits a score for verification. Requires X-Session-Key. The server checks the signature, the nonce (fresh/unused/this-user), that seed matches the issued run seed, then replays seed+input_log and accepts only if the recomputed score matches. Keep-best per player.
Body:
{
  "game_id": "og-testgame",
  "run_id": "<hex>",
  "nonce": "<hex>",
  "board_id": "survival-0",
  "score": 1234,
  "seed": 123456789,
  "input_hash": "<sha256 hex of input_log>",
  "sig": "<hmac hex>",
  "input_log": "0:0,40:2,95:1,..."
}

The input_log is a sparse, ascending list of frame:state changes (state bitmask: 1=left, 2=right). See the SDK's sim/sim-spec.md.

Leaderboards are split per game mode and map. Board ids follow "<mode>-<mapId>" (e.g. survival-0, battle-3, sprint-2). Single-player runs are Survival and must post to survival-<mapId> matching the run's map; a mismatched-map or battle-*/sprint-* board (those are written server-side from multiplayer rounds) is rejected with 400.

Response (accepted):
{ "success": true, "accepted": true, "verified": true,
  "improved": true, "score": 1234, "rank": 1,
  "message": "Verified! New best: 1234" }
Rejections: 401 bad signature/session, 400 nonce/seed/hash mismatch, 409 run already submitted, 410 run expired, 422 score failed replay verification or implausible timing.
GET
/api/hb/leaderboard/:gameId/:boardId
Public top-N for a board. Verified scores only by default.
Query Parameters:
ParameterTypeDefaultDescription
limitinteger100Max entries (1–1000)
offsetinteger0Pagination offset
unverified0|10Set to 1 to include unverified scores
Response:
{
  "game_id": "og-testgame", "board_id": "highscore",
  "name": "Survival Time", "sort": "desc", "unit": "frames",
  "entries": [ { "rank": 1, "name": "Player", "score": 1234, "verified": true } ],
  "total": 1, "limit": 100, "offset": 0, "hasMore": false
}
POST
/api/hb/achievements
Reports a finished run so the server can re-derive and grant achievements. The client may toast achievements locally for instant feedback, but the grant of record happens here: the server replays seed+input_log and unlocks only the achievements its own simulation proves (run-derived) or its own counters prove (cumulative). A forged request with no matching gameplay unlocks nothing. Requires X-Session-Key; idempotent (re-earning never duplicates).
Body:
{
  "game_id": "og-testgame",
  "run_id": "<hex>",
  "seed": 123456789,
  "input_hash": "<sha256 hex of input_log>",
  "sig": "<hmac hex, see ach signing above>",
  "input_log": "0:0,40:2,95:1,..."
}
Response:
{
  "success": true,
  "stats": { "score": 1653, "obstaclesPassed": 42 },
  "unlocked": [ { "id": "survivor_10s", "name": "Getting the Hang of It",
                  "description": "Survive 10 seconds in a single run.",
                  "kind": "run" } ],
  "all": [ "survivor_10s", "survivor_20s", "dodger_25", "first_run" ]
}

unlocked lists only achievements newly granted by this call (clients toast these β€” especially cumulative ones the client can't predict). all is the player's full unlocked set for the game.

Rejections: 401 bad signature/session, 400 hash/seed mismatch or malformed log, 403 run belongs to another user.
GET
/api/hb/games/:gameId/achievements
A game's achievement catalog. If X-Session-Key is supplied, each entry includes whether the caller has unlocked it. kind is "run" (earned in a single run, replay-verified) or "cumulative" (earned from server-tracked account history).
Response:
{
  "game_id": "og-testgame", "game_name": "Test Game",
  "achievements": [
    { "id": "survivor_10s", "name": "Getting the Hang of It",
      "description": "Survive 10 seconds in a single run.",
      "kind": "run", "unlocked": true, "unlocked_count": 3 }
  ]
}
GET
/api/me/hb-achievements
The signed-in player's unlocked achievements across all homebrew games (requires X-Session-Key). Used by the dashboard "Homebrew" view.
GET
/api/hb/games/:gameId/maps
A game's deterministic map catalog (public). Each run records a map_id and is replayed on that map. Response: { game_id, maps: [ { map_id, name } ] }.

Online presence (/api/hb/presence)

A lightweight heartbeat so the site and game can show who/how many players are online. A player counts as online while their last heartbeat is within the window (90s). The heartbeat requires X-Session-Key and is HMAC-signed with "presence|" + game_id, so a client can only mark itself online — the count can't be inflated with fake users.

POST
/api/hb/presence
Heartbeat (call ~every 30s while signed in). Body: { game_id, status?, sig } where status is online/playing/lobby. Returns { success, online_count, window_sec }.
GET
/api/hb/games/:gameId/presence
Who/how many are online for a game (public). Response: { game_id, window_sec, online_count, players: [ { name, status, idle_sec } ] }.

Multiplayer lobbies (/api/hb/mp/*)

Shared-seed competitive rooms for up to 32 players: everyone plays the same server-issued seed + map, pushes live progress, and submits a replay-verified final score (same anti-cheat as the leaderboard). Lobby-mutating calls require X-Session-Key and are HMAC-signed with "mp|" + game_id; the round finish is signed like a score with board id mp.

Game modes. A lobby has a modesurvival (longest run wins), battle (last alive wins, elimination view), or sprint (clear the finish line at target frames). All modes use the same replay-verified run; they differ in win condition + presentation. The lobby object also reports alive_count, winner (once finished), and per-player px (live position for opponent ghosts) and cleared (sprint).

POST
/api/hb/mp/lobby/create
Open a lobby (you become host). Body: { game_id, map_id?, mode?, name?, sig }. Returns { success, lobby }.
GET
/api/hb/mp/lobbies?game_id=&status=
List lobbies for a game (public). status = waiting (default), playing, or all (for a live-match list). Each row includes mode.
GET
/api/hb/mp/lobby/:code
Full lobby state + live standings (public). players is sorted by score; per-member run_id/nonce are never exposed here.
POST
/api/hb/mp/lobby/:code/join
Join a lobby. Body: { sig }. Allowed even mid-round: the new member spectates the active round (spectating:true, not counted alive / for completion) and plays the next one.
POST
/api/hb/mp/lobby/:code/leave
Leave a lobby (host leaving disbands it). Body: { sig }.
POST
/api/hb/mp/lobby/:code/return
After a round, re-arm the lobby back to waiting so the same group can play again (clears the round seed + each member's run state; members stay). Idempotent. Any member may call it.
POST
/api/hb/mp/lobby/:code/map
Host. Change the map while waiting. Body: { map_id }.
POST
/api/hb/mp/lobby/:code/mode
Host. Change the game mode while waiting. Body: { mode } (survival/battle/sprint).
POST
/api/hb/mp/lobby/:code/start
Host. Start the round; server picks the shared seed and issues a run per member. Returns the caller's { seed, map_id, run_id, nonce, lobby }.
POST
/api/hb/mp/lobby/:code/me
Fetch your run for the round (run_id/nonce are per-member secrets). Returns { run_id, nonce, seed, map_id, status, lobby }.
POST
/api/hb/mp/lobby/:code/progress
Push your live { frame, score, px, alive }; returns current standings. Cosmetic only (px drives opponent ghosts) β€” never used to award anything.
POST
/api/hb/mp/lobby/:code/finish
Submit your final round score (replay-verified). Body: { score, seed, input_hash, sig, input_log }. When all members finish, the lobby becomes finished.
GET
/api/hb/games
Lists approved homebrew games.
GET
/api/hb/games/:gameId
Game info plus its leaderboards (board_id, name, sort, unit).
GET
/api/me/hb-scores
The signed-in player's submitted homebrew scores (requires X-Session-Key). Used by the dashboard "Homebrew Scores" view.
POST
/api/hb/dev/games
Register a game for approval (requires X-Session-Key). Body: { game_id, name, description?, verify_mode? }. Returns a pending record; an admin then approves it and issues the one-time game_secret.
POST
/api/hb/admin/games/:id/approve
Admin. Approves a game and returns its one-time game_secret (store it now; bake it into the game binary). Also creates a default board if none exist.
POST
/api/hb/admin/games/:id/boards
Admin. Create a leaderboard. Body: { board_id, name, sort: "desc"|"asc", unit? }.
DELETE
/api/hb/admin/games/:id
Admin. Delete a game and all its boards, runs, and scores.

Error Responses

Endpoints may return:

{
  "error": "Error message description"
}

Common HTTP status codes:

Rate Limiting

There are no rate limits on the public API. Please use it responsibly.