{"openapi":"3.0.0","info":{"title":"BeatKhana API","version":"1.0.0","description":"BeatKhana's REST API powers the website, the overlay editor, bots and third-party integrations.\nUse it for durable reads and changes; use the **[Socket.IO API](/socket/docs)** for anything that must update live\n(match coordination, overlays, co-editing).\n\n## Base URLs\n\n| Environment | REST | Socket.IO |\n| --- | --- | --- |\n| Production | `https://api.beatkhana.com/api` | `wss://wss.beatkhana.com` |\n| Staging | `https://api.staging.beatkhana.com/api` | `wss://wss.staging.beatkhana.com` |\n| Local | `http://localhost:3001/api` | `ws://localhost:48522` |\n\n## Authentication\n\nSend a BeatKhana access token as `Authorization: Bearer <token>`. Tokens come from the website's Discord sign-in\nor from an **OAuth 2.0 application** (authorization code, optional S256 PKCE; see the OAuth 2.0 tag and the\n[OAuth guide](https://wiki.beatkhana.com/docs/oauth)). Public `GET` endpoints work without a token.\n\nTokens carry **scopes**, which limit what an application may call:\n\n| Scope | Allows |\n| --- | --- |\n| `rest:user:read` / `rest:user:write` | The signed-in user's own profile and settings |\n| `rest:read` | Every REST read (implies `rest:user:read`) |\n| `rest:write` | Every REST mutation (implies REST read and user read/write) |\n| `ws:read` / `ws:write` | Socket subscriptions / socket commands |\n\nScopes never grant roles. The user's **global roles** (e.g. `role:dev`, `global:tournaments:manage`) and\n**tournament roles** (organiser, admin, staff, head coordinator, coordinator, referee, pooler, caster, host…) decide\nwhether each action is allowed. Operations list the required scope as `x-required-scopes`.\n\n## Identifiers\n\n- **GUIDs** (UUID v4) identify every record and are what relationships use.\n- Tournaments also have a short URL **id** (e.g. `EUC26`); parameters named `identifier` accept either.\n- Users can be addressed by **GUID, Discord ID or any linked BeatLeader/ScoreSaber ID**; `@me` means the signed-in user.\n\n## Responses and errors\n\nSuccessful responses are JSON. Failures use HTTP status codes and usually a **plain-text** message written for people\n(some newer endpoints answer `{ \"error\": \"…\" }`). Always check the status before parsing:\n\n| Status | Meaning |\n| --- | --- |\n| 400 | Invalid input; the message says what to fix |\n| 401 | Missing, invalid or expired token, or a missing scope |\n| 403 | Signed in, but your roles do not allow this |\n| 404 | Not found (or not visible to you) |\n| 409 | Conflict with newer state, e.g. a reorder against a changed list; reload and retry |\n| 429 | Rate limited; back off |\n\nTimestamps are ISO 8601 UTC instants. Dates stored as \"unset\" may appear as the Unix epoch; only dates after 2020 are meaningful.\n\n## More\n\n- [Socket.IO packet reference](/socket/docs)\n- [Backend architecture](https://wiki.beatkhana.com/docs/backend-architecture) · [Developer guides](https://wiki.beatkhana.com/docs/developers)\n- TypeScript client: `bk-client` (typed wrappers for REST and sockets).","contact":{"name":"BeatKhana Discord","url":"https://discord.gg/AeQutbwDNj"}},"servers":[{"url":"https://api.beatkhana.com/api","description":"Production server"},{"url":"http://localhost:3001/api","description":"Development server"},{"url":"https://api.staging.beatkhana.com/api","description":"Staging server"}],"components":{"securitySchemes":{"BeatKhanaAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"BeatKhana authentication token"},"OAuthClient":{"type":"http","scheme":"basic","description":"OAuth client_id and client_secret using HTTP Basic authentication."},"BeatKhanaOAuth":{"type":"oauth2","description":"OAuth 2.0 authorization-code flow with refresh-token rotation and optional PKCE S256.","flows":{"authorizationCode":{"authorizationUrl":"https://api.beatkhana.com/api/oauth/authorize","tokenUrl":"https://api.beatkhana.com/api/oauth/token","refreshUrl":"https://api.beatkhana.com/api/oauth/token","scopes":{"rest:user:read":"Read the signed-in user's profile.","rest:user:write":"Update the signed-in user's profile and account settings.","rest:read":"Read REST resources including tournaments; implies rest:user:read.","rest:write":"Mutate REST resources where roles permit; implies REST read and user read/write.","ws:read":"Subscribe to read-only websocket events.","ws:write":"Send websocket commands; implies ws:read.","tournamentassistant":"Use TournamentAssistant client functionality.","tournamentassistant:game":"Authenticate TournamentAssistant game clients.","compcube":"Access the CompCube website, backend, and competitive system."}}}}},"schemas":{"OAuthScope":{"type":"string","enum":["ws:read","ws:write","rest:user:read","rest:user:write","rest:read","rest:write","tournamentassistant","tournamentassistant:game","compcube"],"description":"Token capability. Write scopes imply their corresponding read scopes; rest:read implies rest:user:read; rest:write implies all REST user scopes."},"UserCard":{"type":"object","description":"Compact public user summary for hover cards. Never contains private relations.","required":["guid","discordId","username","preferredName","avatarUrl","roles","tournamentsPlayed"],"properties":{"guid":{"type":"string","format":"uuid"},"discordId":{"type":"string","example":"123456789012345678"},"username":{"type":"string","example":"luna"},"preferredName":{"type":"string","example":"Luna"},"avatarUrl":{"type":"string","example":"https://cdn.beatkhana.com/view/avatar.webp"},"pronouns":{"type":"string","nullable":true,"example":"she/her"},"country":{"type":"string","nullable":true,"example":"HU","description":"ISO country code from BeatLeader, falling back to ScoreSaber."},"bio":{"type":"string","nullable":true,"description":"Plain-text excerpt of the profile description (max 140 characters)."},"roles":{"type":"array","items":{"type":"string","enum":["owner","developer","organiser"]}},"isBot":{"type":"boolean"},"emailVerified":{"type":"boolean"},"joinedAt":{"type":"string","format":"date-time"},"tournamentsPlayed":{"type":"integer","description":"Distinct publicly listed (approved, ongoing or completed) tournaments played."},"beatLeaderId":{"type":"string","nullable":true},"scoreSaberId":{"type":"string","nullable":true},"twitchName":{"type":"string","nullable":true}}},"AvailabilityStatus":{"type":"string","enum":["available","maybe","unavailable","would_rather_not"],"description":"Available, Maybe, Not Available, and Would Rather Not."},"AvailabilityWindow":{"type":"object","required":["startsAt","endsAt"],"properties":{"guid":{"type":"string","format":"uuid","readOnly":true},"startsAt":{"type":"string","format":"date-time","description":"Inclusive UTC start instant."},"endsAt":{"type":"string","format":"date-time","description":"Exclusive UTC end instant."}}},"AvailabilityResponse":{"type":"object","required":["userGuid","role","status"],"description":"A match response or one canonical 15-minute calendar-slot response. Exactly one target shape is used: matchGuid, or startsAt plus endsAt.","properties":{"guid":{"type":"string","format":"uuid","readOnly":true},"sheetGuid":{"type":"string","format":"uuid","readOnly":true},"userGuid":{"type":"string","format":"uuid","readOnly":true},"role":{"type":"string","example":"role:coordinator","description":"A value from the tournament permissions role enum."},"status":{"$ref":"#/components/schemas/AvailabilityStatus"},"otherReason":{"type":"string","nullable":true,"example":"Available after the preceding match finishes."},"matchGuid":{"type":"string","format":"uuid","nullable":true},"startsAt":{"type":"string","format":"date-time","nullable":true},"endsAt":{"type":"string","format":"date-time","nullable":true},"user":{"type":"object","readOnly":true,"additionalProperties":true,"description":"Joined BeatKhana user record; only returned to authorized tournament staff and applicants."}}},"AvailabilityResponseInput":{"type":"object","required":["status"],"description":"One answer target. Match sheets use matchGuid; day/range sheets use startsAt and endsAt aligned to 15-minute boundaries. Overlapping calendar inputs are expanded into slots and the later input wins. User and role are derived from the authenticated request.","properties":{"status":{"$ref":"#/components/schemas/AvailabilityStatus"},"otherReason":{"type":"string","nullable":true,"description":"Legacy optional note; no answer requires a note."},"matchGuid":{"type":"string","format":"uuid","nullable":true,"description":"Required for a matches-mode sheet."},"startsAt":{"type":"string","format":"date-time","nullable":true,"description":"Required for a days- or range-mode sheet and aligned to a 15-minute boundary."},"endsAt":{"type":"string","format":"date-time","nullable":true,"description":"Exclusive interval end aligned to a 15-minute boundary; required for a days- or range-mode sheet."}}},"TeamAvailabilityResponseInput":{"type":"object","required":["status","startsAt","endsAt"],"description":"One captain's answer. Intervals must cover complete 15-, 30-, or 60-minute slots inside a sheet window. Later overlapping inputs overwrite earlier answers.","properties":{"status":{"type":"string","enum":["available","unavailable","maybe","would_rather_not"]},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"}}},"AvailabilitySheet":{"type":"object","required":["guid","tournamentGuid","createdByGuid","title","mode","roles","timezone"],"properties":{"guid":{"type":"string","format":"uuid"},"tournamentGuid":{"type":"string","format":"uuid"},"createdByGuid":{"type":"string","format":"uuid"},"audience":{"type":"string","enum":["staff","teams"],"default":"staff"},"teamEligibility":{"type":"string","enum":["registered","qualified","both"],"default":"both"},"slotMinutes":{"type":"integer","enum":[15,30,60],"default":15,"description":"Time slots are 15, 30, or 60 minutes."},"acceptedStatuses":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AvailabilityStatus"},"description":"Allowed answer buttons; all four answers are selected by default."},"title":{"type":"string","example":"Grand Finals Availability"},"description":{"type":"string","nullable":true},"mode":{"type":"string","enum":["matches","days","range"]},"roles":{"type":"array","minItems":0,"items":{"type":"string"},"example":["role:coordinator","role:referee"]},"timezone":{"type":"string","example":"Europe/Budapest","description":"IANA timezone used when the sheet was authored. Stored instants remain UTC."},"startsAt":{"type":"string","format":"date-time","nullable":true,"description":"Required only in range mode."},"endsAt":{"type":"string","format":"date-time","nullable":true,"description":"Required only in range mode."},"closesAt":{"type":"string","format":"date-time","nullable":true,"description":"Optional response deadline."},"matches":{"type":"array","readOnly":true,"items":{"type":"object","properties":{"guid":{"type":"string","format":"uuid"},"matchGuid":{"type":"string","format":"uuid"},"match":{"type":"object","additionalProperties":true}}}},"windows":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AvailabilityWindow"}},"responses":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AvailabilityResponse"}},"teamResponses":{"type":"array","readOnly":true,"items":{"type":"object","required":["teamGuid","status","startsAt","endsAt"],"properties":{"teamGuid":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["available","unavailable","maybe","would_rather_not"]},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"}}}},"teamSubmissions":{"type":"array","readOnly":true,"items":{"type":"object","properties":{"teamGuid":{"type":"string","format":"uuid"},"updatedByGuid":{"type":"string","format":"uuid"},"submittedAt":{"type":"string","format":"date-time"}}}},"eligibleTeams":{"type":"array","readOnly":true,"items":{"$ref":"#/components/schemas/AvailabilityTeam"}},"createdBy":{"type":"object","readOnly":true,"additionalProperties":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"AvailabilitySheetInput":{"type":"object","required":["title","mode","timezone"],"description":"Targets depend on mode: matches requires matchGuids; days requires windows; range requires startsAt and endsAt.","properties":{"audience":{"type":"string","enum":["staff","teams"],"default":"staff"},"teamEligibility":{"type":"string","enum":["registered","qualified","both"],"default":"both"},"slotMinutes":{"type":"integer","enum":[15,30,60],"default":15,"description":"Time slots are 15, 30, or 60 minutes."},"acceptedStatuses":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/components/schemas/AvailabilityStatus"},"description":"Allowed answer buttons; all four answers are selected by default."},"title":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","nullable":true},"mode":{"type":"string","enum":["matches","days","range"]},"roles":{"type":"array","minItems":0,"uniqueItems":true,"items":{"type":"string"},"example":["role:caster"]},"timezone":{"type":"string","example":"America/New_York"},"startsAt":{"type":"string","format":"date-time","nullable":true},"endsAt":{"type":"string","format":"date-time","nullable":true},"closesAt":{"type":"string","format":"date-time","nullable":true},"matchGuids":{"type":"array","uniqueItems":true,"items":{"type":"string","format":"uuid"}},"windows":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityWindow"}}}},"AvailabilityTeam":{"type":"object","required":["guid","id","name","captain","isSolo","isRegistered","isQualified"],"properties":{"guid":{"type":"string","format":"uuid"},"id":{"type":"string"},"name":{"type":"string"},"captain":{"type":"string","format":"uuid"},"isSolo":{"type":"boolean"},"isRegistered":{"type":"boolean"},"isQualified":{"type":"boolean"}}},"AvailabilityState":{"type":"object","required":["sheets","applicantRoles","manageableRoles","userGuid"],"properties":{"sheets":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilitySheet"}},"applicantRoles":{"type":"array","items":{"type":"string"},"description":"Tournament roles held by the authenticated user."},"manageableRoles":{"type":"array","items":{"type":"string"},"description":"Current UI assignment roles the authenticated head may manage."},"canManageTeams":{"type":"boolean"},"teams":{"type":"array","items":{"$ref":"#/components/schemas/AvailabilityTeam"}},"userGuid":{"type":"string","format":"uuid"}}},"QualifierSyncResult":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"fetchedAt":{"type":"string","format":"date-time"},"keptPreviousCache":{"type":"boolean","description":"True when synchronization failed without replacing the last-known-good TA cache."}}},"QualifierStandings":{"type":"object","description":"Persisted qualifier leaderboard assembled from normalized standings and the current registered/qualified teams.","required":["maps","songAndTeamProcesses","overallTeamStandings","overallStandings","description","syncedAt","updatedAt","lastSyncError"],"properties":{"maps":{"type":"array","items":{"type":"object","additionalProperties":true}},"songAndTeamProcesses":{"type":"array","items":{"type":"object","additionalProperties":true}},"overallTeamStandings":{"type":"array","items":{"type":"object","additionalProperties":true}},"overallStandings":{"type":"array","items":{"type":"object","additionalProperties":true}},"description":{"type":"array","items":{"type":"string"}},"syncedAt":{"type":"string","format":"date-time","nullable":true},"updatedAt":{"type":"string","format":"date-time","nullable":true},"lastSyncError":{"type":"string","nullable":true}}}}},"paths":{"/userRoles":{"get":{"summary":"List every global and tournament role and permission","tags":["Users"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Categorized role and permission values"},"401":{"description":"Missing or invalid authentication"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/admin/users":{"get":{"summary":"List users with tournament role assignments","description":"Owner/developer view. Private stream-key and notification relations are excluded.\n\nRequired token scope: `rest:read`.","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Users and role assignments"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Owner or developer role required"}},"parameters":[],"x-required-scopes":["rest:read"]}},"/admin/users/{identifier}":{"patch":{"summary":"Edit a user's profile as an owner or developer","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Updated user"},"400":{"description":"Invalid or empty update"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Owner or developer role required"},"404":{"description":"User not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"get":{"summary":"Get another user's settings record as an owner or developer","description":"Same shape as `GET /users/@me` (badges, permissions, stream keys, email verification\nrequests) so the settings page can open any user's settings for support work.\nNotifications and team invites are excluded.\n\n\nRequired token scope: `rest:read`.","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The user's settings record"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Owner or developer role required"},"404":{"description":"User not found"}},"x-required-scopes":["rest:read"]}},"/users/{identifier}":{"get":{"summary":"Get user details","description":"Retrieves detailed information about a user. Supports user GUID, Discord ID, or any linked ranking-platform ID. Using @me returns the authenticated user's full profile including notifications, stream keys, and team invites. Other identifiers return basic public profile information.\n\nRequired token scope: `rest:read`.","tags":["Users"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"},"description":"User GUID, Discord ID, linked platform ID, or @me for authenticated user","example":"user-guid-123"}],"responses":{"200":{"description":"User details retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"guid":{"type":"string","example":"user-guid-123"},"discordId":{"type":"string","example":"123456789"},"username":{"type":"string","example":"JohnDoe"},"avatarUrl":{"type":"string","example":"https://cdn.beatkhana.com/view/avatar-file.png"},"description":{"type":"string"},"badges":{"type":"array","items":{"type":"object"}},"permissions":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Bad request - missing identifier or @me used without auth","content":{"text/plain":{"schema":{"type":"string"},"example":"Please specify a user discord id, beatleader id, or guid"}}},"401":{"description":"Unauthorized - invalid token when using @me","content":{"text/plain":{"schema":{"type":"string"},"example":"Authorization failed!"}}},"404":{"description":"User not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Could not find this user!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"An error occurred while fetching your user."}}}},"x-required-scopes":["rest:read"]}},"/users/{identifier}/card":{"get":{"summary":"Get a compact public user card","description":"Small, cacheable public summary used by the website's hover cards. It never includes\npermissions, emails, stream keys, or other private relations. The identifier may be a\nuser GUID, Discord ID, or any linked BeatLeader/ScoreSaber platform ID.\n","tags":["Users"],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"},"example":"123456789012345678"}],"responses":{"200":{"description":"Compact user card","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCard"}}}},"404":{"description":"User not found"}}}},"/userWithOngoingTournaments/{identifier}":{"get":{"summary":"Get user with their ongoing tournaments","description":"Retrieves user information including their player roles. Only tournaments approved for the BeatKhana listing and past the draft phase are included. Supports user GUID, Discord ID, or BeatLeader ID as identifier. Using @me returns the authenticated user's data.\n\nRequired token scope: `rest:read`.","tags":["Users"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"},"description":"User GUID, Discord ID, BeatLeader ID, or @me for authenticated user","example":"user-guid-123"}],"responses":{"200":{"description":"User and tournaments retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"guid":{"type":"string"},"username":{"type":"string"},"playerRoles":{"type":"array","items":{"type":"object","properties":{"tournament":{"type":"object"}}}}}}}}},"400":{"description":"Bad request - missing identifier or @me used without auth","content":{"text/plain":{"schema":{"type":"string"},"example":"Please specify a user discord id, beatleader id, or guid"}}},"401":{"description":"Unauthorized - invalid token when using @me","content":{"text/plain":{"schema":{"type":"string"},"example":"Authorization failed!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"An error occurred while fetching your user."}}}},"x-required-scopes":["rest:read"]}},"/getUserByPlatform/{platformId}":{"get":{"summary":"Get user by ranking-platform ID","description":"Retrieves public user information using any linked BeatLeader or ScoreSaber platform ID.","tags":["Users"],"parameters":[{"in":"path","name":"platformId","required":true,"schema":{"type":"string"},"description":"BeatLeader or ScoreSaber platform ID","example":"76561198000000000"}],"responses":{"200":{"description":"User retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"guid":{"type":"string"},"username":{"type":"string"}}}}}},"400":{"description":"Bad request - missing platform ID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please specify a platform ID"}}},"404":{"description":"User not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Could not find this user!"}}}}}},"/updateUserSettings":{"post":{"summary":"Update authenticated user settings","description":"Updates the authenticated user's profile settings including description, pronouns, headset, grip, preferred name, controllers, and avatar URL. User descriptions are sanitized to prevent malicious content. At least one field must be provided.\n\nRequired token scope: `rest:user:write`.","tags":["Users"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string","description":"User bio/description","example":"I love beat saber!"},"pronouns":{"type":"string","description":"User pronouns","example":"he/him"},"headset":{"type":"string","description":"VR headset used","example":"Meta Quest 3"},"grip":{"type":"string","description":"Controller grip style","example":"Standard"},"preferredName":{"type":"string","description":"Preferred display name","example":"John"},"controllers":{"type":"string","description":"Controller type","example":"Quest Touch"},"avatarUrl":{"type":"string","description":"Custom avatar URL","example":"https://example.com/avatar.png"}}}}}},"responses":{"200":{"description":"Settings updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"msg":{"type":"string","example":"Successfully updated settings!"}}}}}},"400":{"description":"Bad request - missing auth token or no valid settings provided","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! Please request a new token!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to process request"}}}},"parameters":[],"x-required-scopes":["rest:user:write"]}},"/updateUser":{"put":{"summary":"Update user profile from Discord","description":"Synchronizes the authenticated user's profile with their Discord account data. Updates username and avatar URL from Discord. Requires a valid Discord OAuth token in the Discord header.\n\nRequired token scope: `rest:user:write`.","tags":["Users"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"parameters":[{"in":"header","name":"Discord","required":true,"schema":{"type":"string"},"description":"Discord OAuth token (format \"Bearer {token}\")","example":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}],"responses":{"200":{"description":"User profile updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"msg":{"type":"string","example":"Successfully updated settings!"}}}}}},"400":{"description":"Bad request - missing auth token or invalid Discord token","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or Discord API error","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! Please request a new token!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to process request"}}}},"x-required-scopes":["rest:user:write"]}},"/tournaments/{tournamentIdentifier}/updates":{"get":{"summary":"Read public tournament updates with cursor pagination (posting and delivery information available to authorised staff)","tags":["Tournaments"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}},{"in":"query","name":"before","schema":{"type":"string"}}],"responses":{"200":{"description":"Updates","nextCursor":null,"canPost and discordAvailable":null}}},"post":{"summary":"Publish an idempotent tournament update with optional Discord delivery","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["guid","content"],"properties":{"guid":{"type":"string","format":"uuid","description":"Stable identifier reused if a publish request is retried"},"title":{"type":"string","maxLength":120},"content":{"type":"string","minLength":1,"maxLength":16000},"changes":{"type":"array","maxItems":12,"description":"Before/after cards for maps, match times or any player-facing change","items":{"type":"object"}},"sendToDiscord":{"type":"boolean","default":false}}}}}},"responses":{"201":{"description":"Published update and delivery status"},"403":{"description":"Posting permission required"},"409":{"description":"Identifier already belongs to a different update"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/updates/{updateGuid}/discord":{"post":{"summary":"Retry failed or interrupted Discord delivery without resending successful message parts","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Current delivery status"},"403":{"description":"Posting permission required"},"404":{"description":"No Discord delivery requested for this update"}},"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"updateGuid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/updates/{updateGuid}":{"patch":{"summary":"Edit an update, visual change cards and its existing Discord announcement","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"updateGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content","expectedUpdatedAt"],"properties":{"title":{"type":"string","maxLength":120},"content":{"type":"string","maxLength":16000},"changes":{"type":"array","maxItems":12,"items":{"type":"object"}},"expectedUpdatedAt":{"type":"string","format":"date-time","nullable":true}}}}}},"responses":{"200":{"description":"Edited website update"},"403":{"description":"Posting permission required"},"404":{"description":"Update not found in this tournament"},"409":{"description":"The update changed or Discord delivery is still active"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Delete an update and remove its Discord announcement, retaining failed cleanup for retry","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"updateGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expectedUpdatedAt"],"properties":{"expectedUpdatedAt":{"type":"string","format":"date-time","nullable":true}}}}}},"responses":{"200":{"description":"Website update deleted"},"403":{"description":"Posting permission required"},"404":{"description":"Update not found in this tournament"},"409":{"description":"The update changed or Discord delivery is still active"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/registration-eligibility":{"get":{"summary":"Check the current user's tournament registration profile requirements","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"A structured registration eligibility result"},"400":{"description":"Missing authorization or an invalid tournament/user"},"401":{"description":"Invalid authorization"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/register/{teamGuid}":{"post":{"summary":"Register a team for a tournament","description":"Registers a team for a tournament. Captains must satisfy all registration requirements. Tournament-scoped managers may register outside the public registration period but must still satisfy account, roster, capacity, and additional-field requirements. Global tournament administrators may register any team without registration requirement checks. Once registered, the team's player roster is locked for ordinary users.\n\nRequired token scope: `rest:write`.","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"Team GUID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"additionalFields":{"type":"array","description":"Required for team captain self-registration when tournament has required additional fields.","items":{"type":"object","properties":{"fieldGuid":{"type":"string"},"response":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"object","properties":{"hash":{"type":"string"},"difficulty":{"type":"string"},"characteristic":{"type":"string"}}}]}}}}}}}}},"responses":{"200":{"description":"Team registered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Successfully updated the team!"},"newTeam":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required parameters, team not found, or not eligible to register","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament identifier is required"},"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"eligibility":{"type":"object","description":"Structured team and player eligibility failures."}}}}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You are not permitted to add other users to this tournament!"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/unregister/{teamGuid}":{"post":{"summary":"Unregister a team from a tournament","description":"Removes a registered team from a tournament, resetting their status to unregistered. Can be done by the team captain or tournament admins. Registration deadline must not have passed for team captains. Team members retain their player status but are no longer in the tournament's active roster. Does not support @me identifier.\n\nRequired token scope: `rest:write`.","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"Team GUID"}],"responses":{"200":{"description":"Team unregistered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Successfully updated the team!"},"newTeam":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required parameters, team not found, or team already unregistered","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament identifier is required"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You are not permitted to add other users to this tournament!"}}},"403":{"description":"Forbidden - registration deadline has passed for team captains","content":{"text/plain":{"schema":{"type":"string"},"example":"The deadline for registration and revoking a registration has passed."}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/qualify/{teamGuid}":{"post":{"summary":"Qualify a team for a tournament","description":"Moves a registered team to qualified status, advancing it to the main bracket or playoffs. Calling this route for an already-qualified team updates its seed. Can only be done by tournament administrators. Optionally includes a seed for bracket seeding.\n\nRequired token scope: `rest:write`.","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"Team GUID"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"seed":{"type":"integer","nullable":true,"description":"Bracket seed for the qualified team","example":1}}}}}},"responses":{"200":{"description":"Team qualified successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Successfully updated the team!"},"newTeam":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required parameters, team not found, or an unqualified team is not registered","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament identifier is required"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You are not permitted to qualify teams for this tournament!"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/disqualify/{teamGuid}":{"post":{"summary":"Disqualify a team from a tournament","description":"Moves a qualified team back to registered status, removing them from the main bracket or playoffs. Can only be done by tournament admins. Disqualified teams return to standard registration status and can have their roster modified again.\n\nRequired token scope: `rest:write`.","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"Team GUID"}],"responses":{"200":{"description":"Team disqualified successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Successfully updated the team!"},"newTeam":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required parameters, team not found, or team not qualified","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament identifier is required"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You are not permitted to disqualify teams for this tournament!"}}}},"x-required-scopes":["rest:write"]}},"/tournament-platforms":{"get":{"summary":"List tournament platforms","description":"Public list of every platform a tournament can ask to be visible on. BeatKhana is the system platform and is always included in requests.","tags":["Tournament Platforms"],"responses":{"200":{"description":"Public platforms","BeatKhana first":null}},"parameters":[]}},"/tournament-platforms/mine":{"get":{"summary":"List platforms the authenticated user reviews or administers","description":"Owners and developers see every platform with the role `global`. BeatKhana's tournament managers see the BeatKhana platform as reviewers.\n\nRequired token scope: `rest:read`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Platforms with the caller's role"},"401":{"description":"Authentication required"}},"parameters":[],"x-required-scopes":["rest:read"]}},"/admin/tournament-platforms/overview":{"get":{"summary":"Decision overview across every tournament platform","description":"Request counts per platform and status, and the latest requests and decisions. Owners, developers and global tournament managers only.\n\nRequired token scope: `rest:read`.","tags":["Tournament Platforms","Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Overview"},"403":{"description":"BeatKhana administrator required"}},"parameters":[],"x-required-scopes":["rest:read"]}},"/admin/tournament-platforms":{"post":{"summary":"Create a tournament platform","description":"Owners and developers only. Active tournaments that asked to be visible everywhere are added to the new platform's queue.\n\nRequired token scope: `rest:write`.","tags":["Tournament Platforms","Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contextId","name","description","imageUrl"],"properties":{"contextId":{"type":"string","example":"cube-community"},"name":{"type":"string"},"description":{"type":"string"},"imageUrl":{"type":"string"},"websiteUrl":{"type":"string"}}}}}},"responses":{"201":{"description":"Platform created"},"403":{"description":"Owner or developer required"},"409":{"description":"The context ID is taken"}},"parameters":[],"x-required-scopes":["rest:write"]}},"/admin/tournament-platforms/{contextId}":{"delete":{"summary":"Permanently delete a tournament platform","description":"Removes the platform, its members, requests and events. The BeatKhana platform cannot be deleted.\n\nRequired token scope: `rest:write`.","tags":["Tournament Platforms","Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Platform deleted"},"403":{"description":"Owner or developer required"},"409":{"description":"The BeatKhana platform cannot be deleted"}},"x-required-scopes":["rest:write"]}},"/tournament-platforms/{contextId}":{"get":{"summary":"Get a public tournament platform","tags":["Tournament Platforms"],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Public platform"},"404":{"description":"Platform not found"}}},"put":{"summary":"Update a platform's public details","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"imageUrl":{"type":"string"},"websiteUrl":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Platform updated"},"403":{"description":"Platform administrator required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournament-platforms/{contextId}/settings":{"get":{"summary":"Get a platform's private settings and members","description":"Webhook URLs, the subscribed events and the review team. The signing secret is reported only as `configured`.\n\nRequired token scope: `rest:read`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Private settings"},"403":{"description":"Platform administrator required"}},"x-required-scopes":["rest:read"]},"put":{"summary":"Update a platform's webhooks","description":"Send an empty string to remove a URL or the secret. Omitted fields are kept.\n\nRequired token scope: `rest:write`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"discordWebhookUrl":{"type":"string"},"customWebhookUrl":{"type":"string"},"customWebhookSecret":{"type":"string"},"webhookEvents":{"type":"array","items":{"type":"string","enum":["visibility_requested","visibility_approved","visibility_rejected","visibility_blocked","visibility_withdrawn","member_added","member_removed"]}}}}}}},"responses":{"200":{"description":"Settings saved"},"400":{"description":"Invalid webhook URL"},"403":{"description":"Platform administrator required"}},"x-required-scopes":["rest:write"]}},"/tournament-platforms/{contextId}/webhooks/test":{"post":{"summary":"Send a test webhook","description":"Delivers a sample of the chosen event to the configured webhooks, whether or not the event is subscribed. Test bodies carry `\"test\": true`.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"event":{"type":"string"}}}}}},"responses":{"200":{"description":"Delivery results per channel"},"400":{"description":"No webhook configured or unknown event"}},"x-required-scopes":["rest:write"]}},"/tournament-platforms/{contextId}/members":{"post":{"summary":"Add a platform reviewer or administrator, or change their role","description":"Reviewers see and decide visibility requests. Administrators also manage the platform's details, team and webhooks.\n\nRequired token scope: `rest:write`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userIdentifier","role"],"properties":{"userIdentifier":{"type":"string","description":"BeatKhana GUID","Discord ID":null,"BeatLeader or ScoreSaber ID":null},"role":{"type":"string","enum":["admin","reviewer"]}}}}}},"responses":{"200":{"description":"Member saved"},"404":{"description":"User not found"}},"x-required-scopes":["rest:write"]}},"/tournament-platforms/{contextId}/members/{userGuid}":{"delete":{"summary":"Remove a platform member","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"userGuid","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Member removed"},"404":{"description":"Member not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournament-platforms/{contextId}/requests":{"get":{"summary":"List a platform's tournament visibility requests","description":"Reviewers see every request with the tournament it concerns, newest first. Filter with a comma-separated `status`.\n\nRequired token scope: `rest:read`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"query","name":"status","schema":{"type":"string","example":"pending,rejected"}}],"responses":{"200":{"description":"Requests"},"403":{"description":"Platform reviewer required"}},"x-required-scopes":["rest:read"]}},"/tournament-platforms/{contextId}/requests/{requestGuid}/review":{"post":{"summary":"Approve, reject or block a tournament","description":"Decisions can be changed later, for example to revoke an approval. Rejecting and blocking need a reason.\nA rejected tournament may ask again; a blocked one cannot, and its organisers only see \"Rejected\".\nOn the BeatKhana platform, blocking also cancels the tournament.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Platforms"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"requestGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["approved","rejected","blocked"]},"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Decision saved"},"400":{"description":"Invalid decision or missing reason"},"409":{"description":"The request was withdrawn"}},"x-required-scopes":["rest:write"]}},"/tournament-platforms/{contextId}/tournaments":{"get":{"summary":"List the tournaments a platform approved","description":"Public. The same list is available as `GET /tournaments?platform={contextId}`.","tags":["Tournament Platforms"],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Approved tournaments","newest start first":null},"404":{"description":"Platform not found"}}}},"/tournaments/{tournamentIdentifier}/visibility":{"get":{"summary":"Where a tournament asked to be visible","description":"Every platform with this tournament's request status. Organisers see a blocked request as `rejected` with `canRequest: false`\nand no reason; BeatKhana administrators see the real status.\n\n\nRequired token scope: `rest:read`.","tags":["Tournament Visibility"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Scope and per-platform status"},"403":{"description":"Tournament organiser required"}},"x-required-scopes":["rest:read"]},"put":{"summary":"Request visibility on every platform or on chosen platforms","description":"`scope: all` asks every platform, including ones created later while the tournament is active.\n`scope: selected` asks BeatKhana (always included) and the platforms in `platformGuids`; deselected platforms are withdrawn.\nRejected and withdrawn requests are submitted again; blocked platforms are skipped and listed in `blocked`.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Visibility"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scope"],"properties":{"scope":{"type":"string","enum":["all","selected"]},"platformGuids":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Requests submitted; the response has the new overview"},"400":{"description":"Invalid scope or cancelled tournament"},"403":{"description":"Tournament organiser required"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/permissions/{userIdentifier}/grant":{"post":{"summary":"Grant a role to a user for a tournament","description":"Grants the specified role and updates the corresponding link table. The required sender permissions depend on which role is being granted.\n\nRequired token scope: `rest:write`.","tags":["Tournament Permissions"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"},"description":"User GUID or Discord ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"role":{"type":"string","example":"role:admin","enum":["role:admin","role:staff","role:coordinator","role:hcoordinator","role:pooler","role:hpooler","role:caster","role:hcaster","role:host","role:referee"]}}}}}},"responses":{"200":{"description":"Role granted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully granted role:admin to Username"}}}}}},"400":{"description":"Bad request - invalid role, user already has role, or user/tournament not found"},"401":{"description":"Unauthorized - insufficient permissions"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/permissions/{userIdentifier}/revoke":{"delete":{"summary":"Revoke a role from a user for a tournament","description":"Revokes the specified role and removes the entry from the corresponding link table. The required sender permissions depend on which role is being revoked.\n\nRequired token scope: `rest:write`.","tags":["Tournament Permissions"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"},"description":"User GUID or Discord ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"role":{"type":"string","example":"role:admin","enum":["role:admin","role:staff","role:coordinator","role:hcoordinator","role:pooler","role:hpooler","role:caster","role:hcaster","role:host","role:referee"]}}}}}},"responses":{"200":{"description":"Role revoked successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully revoked role:admin from Username"}}}}}},"400":{"description":"Bad request - invalid role, user does not have role, or user/tournament not found"},"401":{"description":"Unauthorized - insufficient permissions"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/timelineEvents":{"put":{"summary":"Update tournament timeline events","description":"Add, update, or remove timeline events for a tournament in a single request.\nEvents with a guid will be updated, events without a guid will be created,\nand existing events not included in the request will be deleted.\nRequires organiser, admin, staff permissions or tournament:settings:edit permission.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Timeline Events"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["timelineEvents"],"properties":{"timelineEvents":{"type":"array","description":"Array of timeline events to create, update, or keep (omitted events will be deleted)","items":{"type":"object","required":["title","icon","startTime"],"properties":{"guid":{"type":"string","description":"GUID of existing event to update (omit for new events)","example":"123e4567-e89b-12d3-a456-426614174000"},"title":{"type":"string","description":"Event title","example":"Group Stage Begins"},"description":{"type":"string","nullable":true,"description":"Event description","example":"The group stage matches will begin"},"icon":{"type":"string","description":"Material icon name for the event","example":"video"},"startTime":{"type":"string","format":"date-time","description":"Event start time (ISO 8601 format)","example":"2025-01-15T10:00:00Z"},"endTime":{"type":"string","format":"date-time","nullable":true,"description":"Event end time (ISO 8601 format, optional)","example":"2025-01-15T18:00:00Z"}}}}}}}}},"responses":{"200":{"description":"Timeline events updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully updated timeline events!"},"events":{"type":"array","description":"All current timeline events after the update","items":{"type":"object"}},"deleted":{"type":"number","description":"Number of events that were deleted","example":2}}}}}},"400":{"description":"Bad request - missing parameters or invalid data","content":{"text/plain":{"schema":{"type":"string"},"example":"Each event must have title, icon, and startTime fields."}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament or user not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Either your user or the referred tournament was not found!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to update timeline events. Please check your data and try again."}}}},"x-required-scopes":["rest:write"]}},"/function_here":{"get":{"summary":"Reserved authenticated endpoint template","deprecated":true,"tags":["Internal"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"204":{"description":"Template handler completed"},"400":{"description":"Missing authentication"},"401":{"description":"Invalid authentication"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/teams":{"post":{"summary":"Create a new team","description":"Creates a new team for a tournament with the specified captain. Users can only create teams for themselves unless they have admin permissions. Each user can only be captain of one team per tournament.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["captainId"],"properties":{"captainId":{"type":"string","description":"User GUID or Discord ID of the team captain (usually yourself)","example":"user-123"},"teamName":{"type":"string","description":"Name of the team (defaults to captain's name + \"'s Team\")","example":"The Champions"},"teamId":{"type":"string","description":"Unique ID for the team (defaults to captain's Discord ID)","example":"champions2024"}}}}}},"responses":{"200":{"description":"Team created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully created team!"},"newTeam":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the captain id. (Usually your own)"}}},"401":{"description":"Unauthorized - invalid token or cannot create team for someone else","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot create a team on behalf of someone else on this tournament."}}},"403":{"description":"Forbidden - captain already has a team or is already on another team","content":{"text/plain":{"schema":{"type":"string"},"example":"This user already has a team for this tournament. You are not allowed to make them another team."}}}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a team","description":"Permanently deletes a team and removes all player roles. Only the team captain or tournament admins can delete teams. All team members will have their registration and player roles removed.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamGuid"],"properties":{"teamGuid":{"type":"string","description":"GUID of the team to delete","example":"team-123-abc"}}}}}},"responses":{"200":{"description":"Team deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully deleted your team and removed all roles"}}}}}},"400":{"description":"Bad request - missing required parameters or team not found","content":{"text/plain":{"schema":{"type":"string"},"example":"You must provide the team guid!"}}},"401":{"description":"Unauthorized - cannot delete team on behalf of someone else","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot delete a team on behalf of someone else on this tournament."}}},"403":{"description":"Forbidden - team already has a team or is already on another team","content":{"text/plain":{"schema":{"type":"string"},"example":"This user already has a team for this tournament. You are not allowed to make them another team."}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/modification-eligibility":{"get":{"summary":"Check whether the current user can modify a team","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Team modification eligibility and lock reason"},"401":{"description":"Invalid authorization"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/invites":{"post":{"summary":"Create a team invitation","description":"Sends an invitation to a user to join a team. Invitations sent by tournament managers or global tournament administrators are accepted immediately and add the user to the roster. Tournament managers must still satisfy the invitee's account and profile requirements; global administrators bypass those registration requirements. Team becomes non-solo when the first player is invited or added.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"GUID of the team"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["inviteeIdentifier"],"properties":{"inviteeIdentifier":{"type":"string","description":"User GUID or Discord ID of the user to invite","example":"user-456"},"message":{"type":"string","description":"Custom invitation message","example":"Join our team for the tournament!"}}}}}},"responses":{"200":{"description":"Invitation sent, or the player was added immediately for an authorized manager","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully invited the user to the team!"},"newInvite":{"type":"object","nullable":true,"description":"Pending invitation. Omitted when the player was added immediately."},"autoAccepted":{"type":"boolean","description":"Whether manager privileges caused the invitation to be accepted immediately."}}}}}},"400":{"description":"Bad request - missing required parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"You must provide the identifier of the user you want to invite."}}},"401":{"description":"Unauthorized - cannot invite on behalf of captain","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot invite a player to the team on behalf of the team captain."}}},"403":{"description":"Forbidden - the roster is locked, the user is already assigned, or a tournament manager's invitee does not meet profile requirements","content":{"text/plain":{"schema":{"type":"string"},"example":"This team has already entered the tournament. Players cannot me modified after registration. Unregister if possible."},"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"profileEligibility":{"type":"object"}}}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/invites/{inviteGuid}/eligibility":{"get":{"summary":"Check whether the current invitee can accept a team invitation","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"inviteGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Profile and invitation eligibility details"},"401":{"description":"Invalid authorization or invitation owner"},"404":{"description":"Invitation not found"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/invites/{inviteGuid}":{"delete":{"summary":"Delete a team invitation","description":"Removes a pending team invitation. Only the team captain or tournament admins can delete invitations. Cannot delete rejected invitations or invitations for registered/qualified teams.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"GUID of the team"},{"in":"path","name":"inviteGuid","required":true,"schema":{"type":"string"},"description":"GUID of the invitation to delete"}],"responses":{"200":{"description":"Invitation removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed the invite!"}}}}}},"400":{"description":"Bad request - missing parameters or invalid invite","content":{"text/plain":{"schema":{"type":"string"},"example":"This is not a valid invite"}}},"401":{"description":"Unauthorized - cannot delete invite on behalf of captain","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot invite a player to the team on behalf of the team captain."}}},"403":{"description":"Forbidden - team registered/qualified or invite already rejected","content":{"text/plain":{"schema":{"type":"string"},"example":"You may not delete an invite that has already been rejected!"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/members/{userIdentifier}":{"delete":{"summary":"Remove a team member","description":"Removes a player from a team and revokes their registration and player roles. Only the team captain or tournament admins can remove members. Cannot remove members from registered or qualified teams.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"GUID of the team"},{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"},"description":"User GUID or Discord ID of the member to remove"}],"responses":{"200":{"description":"Team member removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed the user!"}}}}}},"400":{"description":"Bad request - missing required parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"You must provide the tournament identifier and the user you want to remove identifier!"}}},"401":{"description":"Unauthorized - cannot remove member on behalf of captain","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot remove a player to the team on behalf of the team captain."}}},"403":{"description":"Forbidden - team already registered or qualified","content":{"text/plain":{"schema":{"type":"string"},"example":"This team has already entered the tournament. Players cannot me modified after registration. Unregister if possible."}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/captain":{"put":{"summary":"Change a team's captain","description":"Changes the team captain. This operation is restricted to BeatKhana owners and developers. Registration and player permissions are removed from the previous captain when they are no longer part of the roster; existing roster permissions are retained for captains who are also players.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["captainIdentifier"],"properties":{"captainIdentifier":{"type":"string","description":"BeatKhana GUID or Discord ID of the new captain"}}}}}},"responses":{"200":{"description":"Team captain changed successfully"},"400":{"description":"Invalid tournament, team, or captain"},"401":{"description":"Invalid authorization"},"403":{"description":"Only owners and developers may change team captains, or the new captain already belongs to another team"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}/invites/{inviteGuid}/{action}":{"put":{"summary":"Accept or decline a team invitation","description":"Allows the invited user to accept or decline a team invitation. This endpoint is meant for the invitee only. Accepting adds the user to the team and grants registration roles. Declining marks the invitation as rejected. Users must pass eligibility checks to accept.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"GUID of the team"},{"in":"path","name":"inviteGuid","required":true,"schema":{"type":"string"},"description":"GUID of the invitation"},{"in":"path","name":"action","required":true,"schema":{"type":"string","enum":["accept","decline"]},"description":"Action to perform on the invitation"}],"responses":{"200":{"description":"Invitation action processed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully updated the invite!"},"newInvite":{"type":"object","description":"Returned only when declining"}}}}}},"400":{"description":"Bad request - invalid action or missing parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Invalid action! You may only 'accept' or 'decline' the invitation."}}},"401":{"description":"Unauthorized - cannot act on invite for another user","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot perform an action on an invite on behalf of another user."}}},"403":{"description":"Forbidden - already on a team, failed eligibility, or team status prevents changes","content":{"text/plain":{"schema":{"type":"string"},"example":"You are already a player for a team."}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/teams/{teamGuid}":{"put":{"summary":"Update team details","description":"Updates a team's name, ID, description, and image URL. Only the team captain can modify their team (unless they're admins). Modifications may be restricted by tournament settings. Team IDs must be unique within the tournament.\n\nRequired token scope: `rest:write`.","tags":["Teams"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"teamGuid","required":true,"schema":{"type":"string"},"description":"GUID of the team to update"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","name","imageUrl","description"],"properties":{"id":{"type":"string","description":"Unique team ID","example":"champions2024"},"name":{"type":"string","description":"Team name","example":"The Champions"},"imageUrl":{"type":"string","description":"URL to team logo/image","example":"https://example.com/team-logo.png"},"description":{"type":"string","description":"Team description","example":"We are the best team!"}}}}}},"responses":{"200":{"description":"Team updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully deleted your team and removed all roles"},"newTeam":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required fields or team not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide all of the details for editing a team."}}},"401":{"description":"Unauthorized - cannot modify team or modifications disabled","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot modify a team on behalf of someone else on this tournament."}}},"403":{"description":"Forbidden - team ID already in use by another team","content":{"text/plain":{"schema":{"type":"string"},"example":"You cannot set the id of this tournament to an id of another existing team."}}}},"x-required-scopes":["rest:write"]}},"/game/ta/standalone/latest-version":{"get":{"summary":"Get the latest TournamentAssistant standalone version","tags":["Game"],"responses":{"200":{"description":"Plain-text semantic version","content":{"text/plain":{"schema":{"type":"string","example":"0.1.1"}}}}},"parameters":[]},"put":{"summary":"Update the latest TournamentAssistant standalone version","tags":["Game"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version"],"properties":{"version":{"type":"string","example":"0.1.1"}}}}}},"responses":{"200":{"description":"Version updated"},"400":{"description":"Invalid version"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Caller is not in the two-id allowlist"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/validateMtxStreamKey":{"post":{"summary":"Validate MediaMTX stream key","description":"Webhook endpoint for MediaMTX streaming server to validate stream keys.\nSupports both RTMP (token in URL query) and WHIP (Bearer Token in body).\n\nRegular keys can only publish to the owning user's GUID path.\nFloating keys can publish to configured endpoint names in floatingKeyEndpoints.\n\nWHIP Ingest: https://webrtc.stream.beatkhana.com/live/{guid}/whip with Bearer Token\nRTMP Ingest (regular): rtmp://stream.beatkhana.com/live/{guid}?token={key}\nRTMP Ingest (floating): rtmp://stream.beatkhana.com/live/{endpoint}?token={key}\n","tags":["Streaming"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ip","path","protocol","id","action"],"properties":{"ip":{"type":"string","description":"Client IP address","example":"192.168.1.1"},"user":{"type":"string","description":"Username (if provided)","example":"user123"},"password":{"type":"string","description":"Password (RTMP native auth)","example":"pass123"},"token":{"type":"string","description":"Bearer token (WHIP auth)","example":"9196db24-8cda-4aa7-834c-8894bbb5b0f6"},"path":{"type":"string","description":"Stream path containing either a user GUID (regular key) or endpoint name (floating key)","example":"live/583e1518-9651-4b0a-a568-67c6fae73128"},"protocol":{"type":"string","description":"Streaming protocol","enum":["rtmp","webrtc","hls","rtsp","srt"],"example":"webrtc"},"id":{"type":"string","description":"Connection ID","example":"conn123"},"action":{"type":"string","enum":["publish","read"],"description":"Action being performed","example":"publish"},"query":{"type":"string","description":"Query string (for RTMP token param)","example":"token=abc123"}}}}}},"responses":{"200":{"description":"Stream key is valid","content":{"text/plain":{"schema":{"type":"string","example":"OK"}}}},"401":{"description":"Unauthorized - invalid or missing token","content":{"text/plain":{"schema":{"type":"string","example":"Invalid token"}}}}},"parameters":[]}},"/validateSRSStreamKey":{"post":{"summary":"Validate SRS stream key","description":"Webhook endpoint for SRS (Simple Realtime Server) to validate stream keys. The key is passed as a query parameter. Sends a Discord webhook notification when a stream starts.","tags":["Streaming"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stream":{"type":"string","description":"Stream name or path","example":"/live/mystream"},"param":{"type":"string","description":"Query parameters containing the stream key","example":"key=abc123def456"}}}}}},"responses":{"200":{"description":"Stream key is valid","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"integer","example":0}}}}}},"401":{"description":"Unauthorized - invalid or missing token","content":{"text/plain":{"schema":{"type":"string","example":"Invalid token"}}}}},"parameters":[]}},"/validateSRSStreamKeyWithKeyOnly":{"post":{"summary":"Validate SRS stream key from stream URL","description":"Webhook endpoint for SRS to validate stream keys where the key is embedded directly in the stream URL path. Sends a Discord webhook notification when a stream starts.","tags":["Streaming"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"stream_url":{"type":"string","description":"Full stream URL with key as the last path segment","example":"rtmp://server.com/live/abc123def456"}}}}}},"responses":{"200":{"description":"Stream key is valid","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"integer","example":0}}}}}},"401":{"description":"Unauthorized - invalid or missing token","content":{"text/plain":{"schema":{"type":"string","example":"Invalid token"}}}}},"parameters":[]}},"/endSRSStream":{"post":{"summary":"Handle SRS stream end event","description":"Webhook endpoint for SRS to notify when a stream ends. Sends a Discord webhook notification with stream details.\n\nRequired token scope: `rest:write`.","tags":["Streaming"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"Stream end event data from SRS"}}}},"responses":{"200":{"description":"Stream end event processed successfully","content":{"text/plain":{"schema":{"type":"string","example":"OK"}}}}},"parameters":[],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"]}},"/validateOvenMediaEngineStreamKey":{"post":{"summary":"Validate OvenMediaEngine stream key","description":"Webhook endpoint for OvenMediaEngine to validate stream keys and redirect streams to user-specific URLs. The stream key is extracted from the URL path. Sends Discord webhook notifications for stream events.","tags":["Streaming"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client","request"],"properties":{"client":{"type":"object","properties":{"address":{"type":"string","description":"Client IP address","example":"192.168.1.1"},"port":{"type":"integer","description":"Client port number","example":54321}}},"request":{"type":"object","properties":{"direction":{"type":"string","description":"Stream direction (incoming/outgoing)","example":"incoming"},"protocol":{"type":"string","description":"Streaming protocol","example":"rtmp"},"url":{"type":"string","description":"Stream URL containing the key","example":"rtmp://server.com/live/abc123def456"},"time":{"type":"string","description":"Timestamp of the request","example":"2023-12-06T10:30:00Z"}}}}}}}},"responses":{"200":{"description":"Stream key is valid, includes redirect URL","content":{"application/json":{"schema":{"type":"object","properties":{"allowed":{"type":"boolean","example":true},"new_url":{"type":"string","description":"Redirect URL for the stream","example":"rtmp://stream.beatkhana.com/user/user-guid-123"},"lifetime":{"type":"integer","description":"Stream lifetime in seconds (0 = unlimited)","example":0},"reason":{"type":"string","example":"Stream key validated successfully"}}}}}},"401":{"description":"Unauthorized - invalid or missing stream key","content":{"application/json":{"schema":{"type":"object","properties":{"allowed":{"type":"boolean","example":false},"reason":{"type":"string","example":"Invalid stream key"}}}}}},"500":{"description":"Internal server error during validation","content":{"application/json":{"schema":{"type":"object","properties":{"allowed":{"type":"boolean","example":false},"reason":{"type":"string","example":"Internal server error during validation"}}}}}}},"parameters":[]}},"/streamKeys":{"get":{"summary":"Get all stream key groups","description":"Retrieves all stream key groups with their associated keys, users, and tournaments. Requires global streaming permissions.\n\nRequired token scope: `rest:read`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Stream key groups retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"streamKeyGroups":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Bad request - missing authorization header","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"This user is not permitted to carry out this action!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching your user profile"}}}},"parameters":[],"x-required-scopes":["rest:read"]}},"/streamKeys/{tournamentGuid}":{"get":{"summary":"Get stream keys for a specific tournament","description":"Retrieves all stream key groups and keys associated with a specific tournament. Requires tournament staff permissions or global streaming permissions.\n\nRequired token scope: `rest:read`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"},"description":"Tournament GUID"}],"responses":{"200":{"description":"Stream key groups retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"keyGroups":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Bad request - missing authorization header or tournament GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the tournament guid for which you wish to fetch the stream keys!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You do not have sufficient permissions to view stream keys!"}}},"404":{"description":"Tournament or user not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Either your user account or the tournament was not found!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching your user profile"}}}},"x-required-scopes":["rest:read"]}},"/streamKeys/revoke":{"post":{"summary":"Revoke stream keys globally","description":"Revokes multiple stream keys across all tournaments. Requires global streaming revoke permissions. This is an admin-only endpoint.\n\nRequired token scope: `rest:write`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["streamKeys"],"properties":{"streamKeys":{"type":"array","description":"Array of stream keys to revoke","items":{"type":"object","required":["keyGuid","userGuid"],"properties":{"keyGuid":{"type":"string","description":"GUID of the stream key to revoke","example":"abc-123-def"},"userGuid":{"type":"string","description":"GUID of the user who owns the key","example":"user-456-xyz"}}}}}}}}},"responses":{"200":{"description":"Stream keys revoked successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully revoked stream keys!"},"errors":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Bad request - missing authorization header","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"This user is not permitted to carry out this action!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching your user profile"}}}},"parameters":[],"x-required-scopes":["rest:write"]}},"/streamKeys/revoke/{tournamentGuid}":{"post":{"summary":"Revoke stream keys for a specific tournament","description":"Revokes multiple stream keys for a specific tournament. Requires tournament staff permissions or global streaming revoke permissions. Validates that all keys belong to the specified tournament.\n\nRequired token scope: `rest:write`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"},"description":"Tournament GUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["streamKeys"],"properties":{"streamKeys":{"type":"array","description":"Array of stream keys to revoke","items":{"type":"object","required":["keyGuid","userGuid","groupGuid"],"properties":{"keyGuid":{"type":"string","description":"GUID of the stream key","example":"key-123"},"userGuid":{"type":"string","description":"GUID of the user who owns the key","example":"user-456"},"groupGuid":{"type":"string","description":"GUID of the key group","example":"group-789"}}}}}}}}},"responses":{"200":{"description":"Stream keys revoked successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully revoked stream keys!"},"errors":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Bad request - missing authorization header or tournament GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the tournament guid for which you wish to fetch the stream keys!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You do not have sufficient permissions revoke a stream key!"}}},"404":{"description":"Tournament or user not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Either your user account or the tournament was not found!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching your user profile"}}}},"x-required-scopes":["rest:write"]}},"/streamKeyGroups/create":{"post":{"summary":"Create a stream key group","description":"Creates a new stream key group that can be associated with a tournament or global. Stream key groups organize and categorize stream keys. Requires global streaming permissions.\n\nRequired token scope: `rest:write`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","imageUrl"],"properties":{"name":{"type":"string","description":"Name of the stream key group","example":"Main Stream Keys"},"imageUrl":{"type":"string","description":"URL to the group's image/icon","example":"https://example.com/key-icon.png"},"tournamentGuid":{"type":"string","nullable":true,"description":"Tournament GUID to associate with, or null for global group","example":"tournament-123"}}}}}},"responses":{"200":{"description":"Stream key group created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully added stream key group!"},"errors":{"type":"array","items":{"type":"string"}},"newKeyGroup":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required fields or invalid tournament","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide both the name and imageUrl of the key group!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"This user is not permitted to carry out this action!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching your user profile"}}}},"parameters":[],"x-required-scopes":["rest:write"]}},"/streamKeyGroups/remove":{"put":{"summary":"Remove stream key groups","description":"Permanently deletes one or more stream key groups and all associated stream keys. Requires global streaming permissions.\n\nRequired token scope: `rest:write`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","description":"Array of stream key group GUIDs to remove","items":{"type":"string","example":"group-guid-123"}}}}},"responses":{"200":{"description":"Stream key groups removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed stream key group!"}}}}}},"400":{"description":"Bad request - missing authorization header","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"This user is not permitted to carry out this action!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while trying to removing the key groups!"}}}},"parameters":[],"x-required-scopes":["rest:write"]}},"/streamKeys/create/{tournamentGuid}":{"put":{"summary":"Create a stream key","description":"Creates a new stream key for a user within a specific key group and tournament. Supports regular keys and floating keys. Requires tournament staff permissions or global streaming permissions. If tournamentGuid is not provided, requires global permissions.\n\nRequired token scope: `rest:write`.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":false,"schema":{"type":"string"},"description":"Tournament GUID (optional - if omitted, requires global permissions)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["groupGuid","userIdentifier"],"properties":{"groupGuid":{"type":"string","description":"GUID of the stream key group","example":"group-123"},"userIdentifier":{"type":"string","description":"User GUID or Discord ID","example":"user-456"},"isFloatingKey":{"type":"boolean","description":"When true, key can be used against configured endpoint names instead of user GUID path matching","default":false},"floatingKeyEndpoints":{"type":"array","description":"Optional endpoint allowlist for floating keys. Required when isFloatingKey is true.","items":{"type":"string","example":"castA1"}}}}}}},"responses":{"200":{"description":"Stream key created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully added stream key!"},"errors":{"type":"array","items":{"type":"string"}},"newKey":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required fields, invalid data, or invalid floating key endpoint configuration","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide both the groupGuid and the userGuid in the body!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament mismatch","content":{"text/plain":{"schema":{"type":"string"},"example":"There is a mismatch betweent the key group tournament guid and the provided tournament guid!"}}},"404":{"description":"User not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The user account of the !"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching the user profile to which the key should be added"}}}},"x-required-scopes":["rest:write"]}},"/streamKeyRequests":{"get":{"summary":"List all tournament stream-key requests","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Stream-key requests with tournament","requester":null,"reviewer":null,"and group data":null},"401":{"description":"Authentication required"},"403":{"description":"Global streaming administration permission required"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/streamKeyRequests/{tournamentGuid}":{"get":{"summary":"Get a tournament's stream-key request and assigned groups","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Request and stream-key group state"},"401":{"description":"Authentication required"},"403":{"description":"Tournament management permission required"},"404":{"description":"Tournament not found"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"post":{"summary":"Request a stream-key group for a tournament","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Request created or renewed"},"400":{"description":"A group is already assigned"},"401":{"description":"Authentication required"},"403":{"description":"Tournament management permission required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Cancel a pending tournament stream-key request","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Request cancelled"},"400":{"description":"No pending request exists"},"403":{"description":"Tournament management permission required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/streamKeyRequests/{tournamentGuid}/review":{"post":{"summary":"Approve or reject a tournament stream-key request","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["approved","rejected"]},"note":{"type":"string","maxLength":2000}}}}}},"responses":{"200":{"description":"Request reviewed and group created when approved"},"400":{"description":"Invalid review status"},"403":{"description":"Global streaming administration permission required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/streamKeyGroups/tournament/{tournamentGuid}":{"put":{"summary":"Grant or synchronize a tournament stream-key group","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tournament stream-key group granted"},"403":{"description":"Global streaming administration permission required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/staff":{"get":{"summary":"List the public BeatKhana staff roster","tags":["Staff"],"responses":{"200":{"description":"Public staff members and their free-form roles","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","required":["guid","userGuid","roles","user"],"properties":{"guid":{"type":"string","format":"uuid"},"userGuid":{"type":"string","format":"uuid"},"roles":{"type":"array","items":{"type":"string"}},"user":{"type":"object","properties":{"guid":{"type":"string"},"username":{"type":"string"},"preferredName":{"type":"string","nullable":true},"avatarUrl":{"type":"string"},"pronouns":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"twitchName":{"type":"string","nullable":true}}}}}}}}},"500":{"description":"Staff roster could not be loaded"}},"parameters":[]}},"/admin/staff":{"post":{"summary":"Add a user to the BeatKhana staff roster","tags":["Administration","Staff"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userGuid","roles"],"properties":{"userGuid":{"type":"string","format":"uuid"},"roles":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Staff member created"},"400":{"description":"Invalid user or roles"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Owner or developer role required"},"404":{"description":"User not found"},"409":{"description":"User is already on the roster"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/admin/staff/{guid}":{"put":{"summary":"Replace a BeatKhana staff member's role labels","tags":["Administration","Staff"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roles"],"properties":{"roles":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Staff member updated"},"400":{"description":"Invalid roles"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Owner or developer role required"},"404":{"description":"Staff member not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Remove a user from the BeatKhana staff roster","tags":["Administration","Staff"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Staff member removed"},"401":{"description":"Missing or invalid authentication"},"403":{"description":"Owner or developer role required"},"404":{"description":"Staff member not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/shared-content/roles":{"get":{"summary":"Roles that can be granted access to docs, sheets and forms","tags":["Shared Content"],"responses":{"200":{"description":"List of tournament roles"}},"parameters":[]}},"/tournaments/{identifier}/shared-content":{"get":{"summary":"A tournament's docs, sheets and forms visible to the caller","description":"Docs and sheets the caller can view and forms the caller can edit, view results of, or answer.\n`canCreate` says whether the caller may create docs, sheets and forms for this tournament\n(organisers, admins, staff, heads, global owners/developers).\n","tags":["Shared Content"],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ docs, sheets, forms, canCreate }"},"404":{"description":"Tournament not found"}}}},"/shared-content/mine":{"get":{"summary":"The caller's docs, sheets and forms","description":"Everything shared with the caller: docs, sheets and forms they created, were added to, or can\nopen through a role in one of their tournaments; open forms they can answer; and, for global\nowners/developers, everything without a tournament.\n\n\nRequired token scope: `rest:read`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"{ docs, sheets, forms, canCreateUnattached }"},"401":{"description":"Not signed in"}},"parameters":[],"x-required-scopes":["rest:read"]}},"/shared-content/mine/summary":{"get":{"summary":"How much is shared with the caller","description":"Counts of the lists in `/shared-content/mine`, for deciding whether to show a link to them. `hasContent` is also true for people who can create unattached docs.\n\nRequired token scope: `rest:read`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"{ docs, sheets, forms, hasContent }"},"401":{"description":"Not signed in"}},"parameters":[],"x-required-scopes":["rest:read"]}},"/shared-docs":{"post":{"summary":"Create a doc","description":"With `tournament` (GUID or id) the doc belongs to that tournament and needs organiser, admin,\nstaff or head access there. Without it, only global owners/developers may create it.\nText is then co-edited live through the `doc` collaboration scope.\n\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"string","description":"Tournament GUID or id (optional)"},"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"content":{"type":"string","description":"Markdown"},"bannerUrl":{"type":"string","nullable":true},"backgroundUrl":{"type":"string","nullable":true,"description":"Shown blurred behind the doc"},"viewerRoles":{"type":"array","items":{"type":"string"},"description":"Tournament roles that may view"},"editorRoles":{"type":"array","items":{"type":"string"},"description":"Tournament roles that may edit"}}}}}},"responses":{"200":{"description":"The created doc"},"403":{"description":"Not allowed to create docs here"}},"parameters":[],"x-required-scopes":["rest:write"]}},"/shared-docs/{guid}":{"get":{"summary":"Read a doc","description":"Works signed out for docs published with link access. Includes the caller's `access` (canView, canEdit, canManage).","tags":["Shared Content"],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The doc"},"401":{"description":"Sign in to view this doc"},"403":{"description":"No access"},"404":{"description":"Not found"}}},"patch":{"summary":"Change a doc's settings","description":"Editors may change `bannerUrl` and `backgroundUrl`. Managers (owners, tournament staff, the\ncreator) may also change `status` (draft/published), `linkAccess` (none/view/edit, applies once\npublished), `viewerRoles` and `editorRoles`. Open live sessions receive the new settings.\n\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The updated doc"},"403":{"description":"Not allowed"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a doc","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"},"403":{"description":"Managers only"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/shared-docs/{guid}/collaborators":{"post":{"summary":"Give someone access to a doc","description":"Adds (or changes) an individual collaborator, who does not need to be part of the tournament. They get a notification.\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"type":"string","description":"BeatKhana GUID","Discord ID or linked platform ID":null},"access":{"type":"string","enum":["view","edit"],"default":"view"}}}}}},"responses":{"200":{"description":"The updated doc"},"404":{"description":"User not found"}},"x-required-scopes":["rest:write"]}},"/shared-docs/{guid}/collaborators/{userGuid}":{"delete":{"summary":"Remove a collaborator from a doc","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"userGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The updated doc"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/shared-sheets":{"post":{"summary":"Create a sheet","description":"Same creation rules as docs. A new sheet has one tab of 100 rows and 26 columns. Cells, rows,\ncolumns and tabs are then changed live through the `sheet` collaboration scope\n(`collab:sheet:change`); title and description are live text.\n\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"string","description":"Tournament GUID or id (optional)"},"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":2000},"viewerRoles":{"type":"array","items":{"type":"string"},"description":"Tournament roles that may view"},"editorRoles":{"type":"array","items":{"type":"string"},"description":"Tournament roles that may edit"}}}}}},"responses":{"200":{"description":"The created sheet"},"403":{"description":"Not allowed to create sheets here"}},"parameters":[],"x-required-scopes":["rest:write"]}},"/shared-sheets/{guid}":{"get":{"summary":"Read a sheet","description":"Includes every tab's cells (`data`) and the caller's `access`. Works signed out for sheets published with link access.","tags":["Shared Content"],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The sheet"},"401":{"description":"Sign in to view this sheet"},"403":{"description":"No access"},"404":{"description":"Not found"}}},"patch":{"summary":"Change a sheet's sharing","description":"Managers (owners, tournament staff, the creator) may change `status` (draft/published), `linkAccess` (none/view/edit, applies once published), `viewerRoles` and `editorRoles`. Open live sessions receive the new settings.\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The updated sheet"},"403":{"description":"Not allowed"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a sheet","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"},"403":{"description":"Managers only"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/shared-sheets/{guid}/collaborators":{"post":{"summary":"Give someone access to a sheet","description":"Adds (or changes) an individual collaborator, who does not need to be part of the tournament. They get a notification.\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"type":"string","description":"BeatKhana GUID","Discord ID or linked platform ID":null},"access":{"type":"string","enum":["view","edit"],"default":"view"}}}}}},"responses":{"200":{"description":"The updated sheet"},"404":{"description":"User not found"}},"x-required-scopes":["rest:write"]}},"/shared-sheets/{guid}/collaborators/{userGuid}":{"delete":{"summary":"Remove a collaborator from a sheet","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"userGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The updated sheet"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/shared-forms":{"post":{"summary":"Create a form","description":"Same creation rules as docs. `template: \"feedback\"` starts from a ready-made post-tournament\nfeedback form (overall rating, most fun pool and why, favourite map, match smoothness,\nBeatSaver map suggestions, play again, improvements). Questions are then edited live through\nthe `form` collaboration scope.\n\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tournament":{"type":"string"},"title":{"type":"string"},"template":{"type":"string","enum":["blank","feedback"]},"headerImageUrl":{"type":"string","nullable":true},"editorRoles":{"type":"array","items":{"type":"string"}},"viewerRoles":{"type":"array","items":{"type":"string"},"description":"Roles that may view responses"}}}}}},"responses":{"200":{"description":"The created form"}},"parameters":[],"x-required-scopes":["rest:write"]}},"/shared-forms/{guid}":{"get":{"summary":"Read a form for editing","description":"Full definition, the caller's access and the tournament context (pools, maps, teams) for editors and result viewers. Respondents use `/respond`.\n\nRequired token scope: `rest:read`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ form, access, context }"},"403":{"description":"Not an editor or result viewer"}},"x-required-scopes":["rest:read"]},"patch":{"summary":"Change a form's settings","description":"`headerImageUrl`, `status` (draft/open/closed), `audience` (anyone, signed_in, participants,\nstaff), `oneResponsePerUser`, `allowResponseEdits`, `anonymousResponses`, `closesAt`, and for\nmanagers `editorRoles` / `viewerRoles`. Open live sessions receive the new settings.\n\n\nRequired token scope: `rest:write`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The updated form"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a form and its responses","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/shared-forms/{guid}/respond":{"get":{"summary":"Load a form to answer it","description":"The form, tournament context for pool/map/team questions, the caller's access and their previous response (when one response per user and edits are allowed). Works signed out for `anyone` forms.","tags":["Shared Content"],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ form, context, access, myResponse }"},"401":{"description":"Sign in to answer"},"403":{"description":"Closed","or not in the audience":null}}}},"/shared-forms/{guid}/responses":{"post":{"summary":"Submit (or update) a response","description":"`answers` maps question GUIDs to `{ value, reason? }`. Values by type: text and date → string;\nnumber, scale, rating → number; choice/dropdown → `{ selected: [optionGuid], other? }`;\nBeatSaver map → `[{ key, hash, name, artist, mapper, coverUrl }]`; tournament pool/map/team →\n`[guid]`. With one response per user, a second submission replaces the first when edits are\nallowed. No token is needed for `anyone` forms.\n","tags":["Shared Content"],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"answers":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"{ responseGuid, edited, confirmationMessage }"},"400":{"description":"An answer is missing or invalid; the message names the question"},"409":{"description":"Already answered and edits are not allowed"}}},"get":{"summary":"All responses","description":"For editors and result viewers. Respondents are hidden for anonymous forms.\n\nRequired token scope: `rest:read`.","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ form, context, access, responses }"}},"x-required-scopes":["rest:read"]}},"/shared-forms/{guid}/responses.csv":{"get":{"summary":"Export responses as CSV","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"CSV file","content":{"text/csv":{}}}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/shared-forms/{guid}/responses/{responseGuid}":{"delete":{"summary":"Delete one response","tags":["Shared Content"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"responseGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/roster-control":{"get":{"summary":"Roster control status, automatic approval checks and request history","description":"Roster control lets tournament organisers, admins and staff add players and teams directly instead of through invitations. Reading the status re-runs the automatic approval checks. Available to registration managers and BeatKhana tournament administrators.\n\nRequired token scope: `rest:read`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Status (not_requested, pending, approved, denied), checks, history, the caller's access and whether they can request or decide"},"403":{"description":"Not a registration manager of this tournament"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/roster-control/request":{"post":{"summary":"Request roster control for a tournament","description":"Approves immediately when the automatic checks pass. Otherwise marks the tournament pending and notifies BeatKhana staff through the Discord webhook. After a denial a new request can be sent after 24 hours.\n\nRequired token scope: `rest:write`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","minLength":10,"maxLength":1000}}}}}},"responses":{"200":{"description":"New status and whether it was approved automatically"},"403":{"description":"Not a registration manager of this tournament"},"409":{"description":"A request is already pending"},"429":{"description":"Requested too soon after a denial"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/roster-control/decision":{"post":{"summary":"Approve or deny roster control","description":"BeatKhana tournament administrators only. Emails the requester and organisers with the decision.\n\nRequired token scope: `rest:write`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["approve","deny"]},"note":{"type":"string","maxLength":1000}}}}}},"responses":{"200":{"description":"New status"},"403":{"description":"BeatKhana tournament administrator required"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/teasers":{"post":{"summary":"Add a hidden map placeholder to an unpublished pool (multipart details JSON and optional cover)","tags":["Maps"],"responses":{"200":{"description":"Public teaser presentation","with only a processed cover and no source identity":null},"403":{"description":"Map pool management permission required"},"409":{"description":"Pool must be unpublished"}},"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/teasers/{teaserGuid}":{"put":{"summary":"Edit an unpublished hidden map placeholder","tags":["Maps"],"responses":{"200":{"description":"Updated public teaser"}},"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teaserGuid","required":true,"schema":{"type":"string"}}],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Remove an unpublished hidden map placeholder","tags":["Maps"],"responses":{"200":{"description":"Placeholder removed"}},"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teaserGuid","required":true,"schema":{"type":"string"}}],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/teasers/import":{"post":{"summary":"Import private BeatSaver details for preparing a teaser (staff only; source is never persisted)","tags":["Maps"],"responses":{"200":{"description":"Source names and a re-encoded cover for this editing session"}},"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/teasers/preview":{"post":{"summary":"Preview a server-processed cover without publishing or storing the source (multipart cover and effect)","tags":["Maps"],"responses":{"200":{"description":"Processed WebP data URL"}},"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/qualifiers/integration":{"get":{"summary":"Get the TournamentAssistant qualifier integration","description":"Returns the manually configured webhook URL and signing secret plus the selected TA GUIDs and cache status. The signing secret is only returned to authorized tournament managers and is never included in public tournament responses.\n\nRequired token scope: `rest:read`.","tags":["Tournament Qualifiers"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"BeatKhana tournament GUID or public ID."}],"responses":{"200":{"description":"Qualifier integration settings and cache status.","content":{"application/json":{"schema":{"type":"object","required":["webhookUrl","signingSecret","taTournamentGuid","taQualifierGuid"],"properties":{"webhookUrl":{"type":"string","format":"uri"},"signingSecret":{"type":"string","description":"Secret used by TA to create X-TA-Signature-256."},"taTournamentGuid":{"type":"string","nullable":true},"taQualifierGuid":{"type":"string","nullable":true},"lastSyncedAt":{"type":"string","format":"date-time","nullable":true},"lastUpdatedAt":{"type":"string","format":"date-time","nullable":true},"lastSyncError":{"type":"string","nullable":true}}}}}},"401":{"description":"Authentication required or invalid."},"403":{"description":"Tournament qualifier-management permission required."},"404":{"description":"Tournament not found."}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/qualifiers/integration/rotate-secret":{"post":{"summary":"Rotate the TournamentAssistant webhook signing secret","description":"Generates a new 256-bit signing secret. The new secret must be copied into the corresponding TA webhook registration before subsequent deliveries will be accepted.\n\nRequired token scope: `rest:write`.","tags":["Tournament Qualifiers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"BeatKhana tournament GUID or public ID."}],"responses":{"200":{"description":"Signing secret rotated.","content":{"application/json":{"schema":{"type":"object","required":["success","signingSecret","webhookUrl"],"properties":{"success":{"type":"string"},"signingSecret":{"type":"string"},"webhookUrl":{"type":"string","format":"uri"}}}}}},"401":{"description":"Authentication required or invalid."},"403":{"description":"Tournament qualifier-management permission required."},"404":{"description":"Tournament not found."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/qualifiers/sync":{"post":{"summary":"Force synchronization of a TournamentAssistant qualifier","description":"Fetches the selected TA qualifier's /all endpoint, replaces the last-known-good JSONB cache only after a valid response, recalculates relational standings, and refreshes cached map metadata. A failed fetch preserves the existing cache and standings.\n\nRequired token scope: `rest:write`.","tags":["Tournament Qualifiers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"BeatKhana tournament GUID or public ID."}],"responses":{"200":{"description":"Qualifier data synchronized and standings recalculated.","content":{"application/json":{"schema":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean","enum":[true]},"message":{"type":"string"},"fetchedAt":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Authentication required or invalid."},"403":{"description":"Tournament qualifier-management permission required."},"404":{"description":"Tournament not found."},"502":{"description":"TA fetch or recalculation failed; the previous cache is retained.","content":{"application/json":{"schema":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean","enum":[false]},"message":{"type":"string"},"keptPreviousCache":{"type":"boolean"}}}}}}},"x-required-scopes":["rest:write"]}},"/webhooks/tournamentassistant/qualifiers/{tournamentGuid}":{"post":{"summary":"Receive a TournamentAssistant qualifier score webhook","description":"Accepts TA's qualifierScoreSubmitted envelope. The exact request body must be signed with HMAC-SHA256 using the configured secret; the digest is sent as `sha256=<lowercase hex>` in X-TA-Signature-256. Duplicate X-TA-Webhook-Delivery IDs are acknowledged without applying the score twice.","tags":["Tournament Qualifiers","Webhooks"],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string","format":"uuid"},"description":"BeatKhana tournament GUID embedded in the configured webhook URL."},{"in":"header","name":"X-TA-Webhook-Event","required":true,"schema":{"type":"string","enum":["qualifierScoreSubmitted"]}},{"in":"header","name":"X-TA-Webhook-Delivery","required":true,"schema":{"type":"string"},"description":"TA delivery identifier used for replay protection."},{"in":"header","name":"X-TA-Signature-256","required":true,"schema":{"type":"string","pattern":"^sha256=[0-9a-f]{64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","timestamp","tournamentId","oneOfKind","data"],"properties":{"id":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"tournamentId":{"type":"string"},"oneOfKind":{"type":"string","enum":["qualifierScoreSubmitted"]},"data":{"type":"object","required":["qualifierScoreSubmitted"],"properties":{"qualifierScoreSubmitted":{"type":"object","required":["tournamentId","event","map","qualifierScore"],"properties":{"tournamentId":{"type":"string"},"event":{"type":"object","additionalProperties":true},"map":{"type":"object","description":"TA GameplayParameters used for the submitted score. This is not a qualifier Map wrapper and therefore has no map GUID; qualifierScore.mapId identifies the cached qualifier map.","additionalProperties":true},"qualifierScore":{"type":"object","required":["eventId","mapId","platformId"],"additionalProperties":true,"properties":{"eventId":{"type":"string"},"mapId":{"type":"string"},"platformId":{"type":"string"},"username":{"type":"string"},"modifiedScore":{"type":"integer"},"multipliedScore":{"type":"integer"},"maxPossibleScore":{"type":"integer"},"accuracy":{"type":"number"},"notesMissed":{"type":"integer"},"badCuts":{"type":"integer"},"goodCuts":{"type":"integer"},"maxCombo":{"type":"integer"},"fullCombo":{"type":"boolean"}}}}}}}}}}}},"responses":{"200":{"description":"Duplicate delivery already processed."},"202":{"description":"Score accepted","cache updated":null,"and standings recalculated.":null},"400":{"description":"Unsupported event","missing/mismatched delivery ID":null,"mismatched tournament or qualifier":null,"missing map ID":null,"or a map outside the cached qualifier.":null},"401":{"description":"Missing or invalid HMAC signature."},"404":{"description":"Qualifier integration not configured."},"409":{"description":"No matching initial full-sync cache exists for the selected qualifier."},"500":{"description":"Webhook processing or persistence failed."}}}},"/tournaments/{tournamentIdentifier}/pools":{"post":{"summary":"Create a new map pool","description":"Creates a new map pool for a tournament. Map pools are collections of maps used for matches. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","imageUrl"],"properties":{"name":{"type":"string","description":"Name of the map pool","example":"Semifinals Pool"},"imageUrl":{"type":"string","description":"URL to the pool's image/banner","example":"https://example.com/pool-banner.png"}}}}}},"responses":{"200":{"description":"Pool created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully created the map pool!"},"newPool":{"type":"object"}}}}}},"400":{"description":"Bad request - missing authorization header or required fields","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a pool name!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}":{"delete":{"summary":"Delete a map pool","description":"Permanently deletes a map pool and all its associated maps from a tournament. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool to delete"}],"responses":{"200":{"description":"Pool deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully deleted the map pool!"}}}}}},"400":{"description":"Bad request - missing authorization header or pool GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a map pool guid!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]},"put":{"summary":"Publish a map pool","description":"Changes a map pool's status from 'drafted' to 'published', making it visible to participants and available for use in matches. Requires head pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool to publish"}],"responses":{"200":{"description":"Pool published successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully published map pool!"}}}}}},"400":{"description":"Bad request - missing authorization header or pool GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the map pool GUID!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament or pool not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The targeted map pool does not exist!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/flair":{"post":{"summary":"Create a map flair","description":"Creates a new map flair for a tournament. Flairs are visual tags/categories that can be assigned to maps (e.g., \"Tech\", \"Speed\", \"Accuracy\"). Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","color","icon"],"properties":{"name":{"type":"string","description":"Name of the flair","example":"Tech"},"color":{"type":"string","description":"Hex color code for the flair","example":"#FF5733"},"icon":{"type":"string","description":"Icon identifier or URL for the flair","example":"wrench"}}}}}},"responses":{"200":{"description":"Flair created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully added a map flair!"},"flair":{"type":"object"}}}}}},"400":{"description":"Bad request - missing authorization header or tournament identifier","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/flair/{flairGuid}":{"patch":{"summary":"Modify a map flair","description":"Updates an existing map flair's properties including name, color, and icon. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"flairGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the flair to modify"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","color","icon"],"properties":{"name":{"type":"string","description":"Updated name of the flair","example":"Tech"},"color":{"type":"string","description":"Updated hex color code","example":"#FF5733"},"icon":{"type":"string","description":"Updated icon identifier or URL","example":"wrench"}}}}}},"responses":{"200":{"description":"Flair modified successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully modified the map flair!"},"flair":{"type":"object"}}}}}},"400":{"description":"Bad request - missing authorization header or flair GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a flair guid!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Remove a map flair","description":"Permanently deletes a map flair from a tournament. This will remove the flair from all maps it's assigned to. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"flairGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the flair to remove"}],"responses":{"200":{"description":"Flair removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed the map flair!"}}}}}},"400":{"description":"Bad request - missing authorization header or flair GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a flair guid!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to delete flair from database, please try again later and report this."}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/unpublish":{"put":{"summary":"Unpublish a map pool","description":"Changes a map pool's status from 'published' to 'drafted', making it invisible to participants and unavailable for use in matches. Requires head pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool to unpublish"}],"responses":{"200":{"description":"Pool unpublished successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully unpublished map pool!"}}}}}},"400":{"description":"Bad request - missing authorization header or pool GUID","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the map pool GUID!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament or pool not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The targeted map pool does not exist!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/admin/platform/overview":{"get":{"summary":"Live platform overview","description":"Owner/developer dashboard snapshot: the latest minute sample (traffic, latency, sockets,\nprocess, database, streaming, storage), service checks, platform totals and breakdowns,\nbusiest/slowest/failing routes since the process started, and collector health.\n\n\nRequired token scope: `rest:read`.","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Overview snapshot"},"401":{"description":"Not signed in"},"403":{"description":"Owner or developer role required"}},"parameters":[],"x-required-scopes":["rest:read"]}},"/admin/platform/series":{"get":{"summary":"Platform metric history","description":"Minute samples averaged into about 240 buckets across the range. Every metric of the overview sample is included.\n\nRequired token scope: `rest:read`.","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"query","name":"range","schema":{"type":"string","enum":["1h","6h","24h","7d","30d","60d"],"default":"6h"}}],"responses":{"200":{"description":"Bucketed series: { range, bucketSeconds, from, to, points: [{ t, samples, ...metrics }] }"},"400":{"description":"Unknown range"},"403":{"description":"Owner or developer role required"}},"x-required-scopes":["rest:read"]}},"/admin/platform/uptime":{"get":{"summary":"Service availability per day","description":"Daily availability of the API, websocket server, database, streaming (MediaMTX), object storage\n(MinIO) and Discord bot, an overall percentage per check, and incidents from the last 14 days.\nA minute without a sample counts as API downtime. Checks that are not configured are null.\n\n\nRequired token scope: `rest:read`.","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"query","name":"days","schema":{"type":"integer","minimum":1,"maximum":90,"default":60}}],"responses":{"200":{"description":"Uptime summary","daily availability and incidents":null},"403":{"description":"Owner or developer role required"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/picksbansflow":{"post":{"summary":"Create a picks and bans flow configuration","description":"Creates a new picks and bans flow configuration for a tournament. Flow configurations define the order and rules for map selection during matches. Requires coordinator permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Picks and Bans"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["flowName","newPicksBansFlow","intendedPoolSize"],"properties":{"flowName":{"type":"string","description":"Name of the picks and bans flow","example":"Best of 5 Flow"},"newPicksBansFlow":{"type":"array","description":"Array defining the pick/ban sequence","items":{"type":"object"}},"intendedPoolSize":{"type":"integer","description":"Expected size of the map pool for this flow","example":9}}}}}},"responses":{"200":{"description":"Flow created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfuly created the new flow!"},"flow":{"type":"object"}}}}}},"400":{"description":"Bad request - missing parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a tournament id"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"403":{"description":"Flow limit reached","content":{"text/plain":{"schema":{"type":"string"},"example":"You have already reached the max picks and bans flow config limit!"}}},"404":{"description":"Tournament or user not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Either your user or the referred tournament was not found!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"An error occurred while creating your flow!"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/picksbansflow/{picksbansFlowGuid}":{"delete":{"summary":"Remove a picks and bans flow configuration","description":"Permanently deletes a picks and bans flow configuration from a tournament. Requires coordinator permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Picks and Bans"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"picksbansFlowGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the picks and bans flow to remove"}],"responses":{"200":{"description":"Flow deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully deleted the picks and bans flow"}}}}}},"400":{"description":"Bad request - missing parameters or flow not associated with tournament","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a picks and bans flow guid."}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament or user not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Either your user or the referred tournament was not found!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"An error occurred while deleting your flow!"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/picksbansflow/{flowGuid}":{"put":{"summary":"Modify a picks and bans flow configuration","description":"Updates an existing picks and bans flow configuration for a tournament. Requires coordinator permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Picks and Bans"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"flowGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the picks and bans flow to modify"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["flowName","newPicksBansFlow","intendedPoolSize"],"properties":{"flowName":{"type":"string","description":"Updated name of the picks and bans flow","example":"Best of 5 Flow"},"newPicksBansFlow":{"type":"array","description":"Updated array defining the pick/ban sequence","items":{"type":"object"}},"intendedPoolSize":{"type":"integer","description":"Updated expected size of the map pool","example":9}}}}}},"responses":{"200":{"description":"Flow updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Updated the flow!"},"flow":{"type":"object"}}}}}},"400":{"description":"Bad request - missing parameters or flow not associated with tournament","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide a picks and bans flow guid"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament completed/cancelled","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament or user not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Either your user or the referred tournament was not found!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"An error occurred while updating your flow!"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/overlays":{"post":{"summary":"Create a new dynamic tournament overlay","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"widthPx":{"type":"integer","default":1920},"heightPx":{"type":"integer","default":1080},"defaultSceneName":{"type":"string"}}}}}},"responses":{"200":{"description":"Created overlay with its default scene document."}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"get":{"summary":"List dynamic tournament overlays","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full overlay documents for overlays the caller may access."}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}":{"get":{"summary":"Get a dynamic tournament overlay","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Full overlay document tree, selected match data and active scene data."}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"delete":{"summary":"Delete a dynamic tournament overlay","description":"Deletes the overlay after the caller confirms the exact overlay name. Related dynamic documents and nodes are removed by database cascades.\n\nRequired token scope: `rest:write`.","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nameConfirmation"],"properties":{"nameConfirmation":{"type":"string"}}}}}},"responses":{"200":{"description":"Overlay deleted."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}/custom-packets":{"get":{"summary":"List the custom packets available to the signed-in operator","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registry containing packets with compiled schemas and per-packet canSend flags, plus canManage and supported placeholders."},"400":{"description":"Invalid definition, missing token, or unavailable overlay."},"401":{"description":"Authentication is required."}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"post":{"summary":"Register a typed custom packet for an overlay","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"description":"Hosts and tournament management can configure packets. The payload type is a restricted JSON-compatible TypeScript type, parsed without execution. Extra keys are preserved when sending.\n\nRequired token scope: `rest:write`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["packetType","name","schemaSource","allowedRoles","presets"],"properties":{"packetType":{"type":"string","maxLength":80},"name":{"type":"string","maxLength":120},"description":{"type":"string","maxLength":2000},"schemaSource":{"type":"string","maxLength":10000,"example":"{ category: string; }"},"allowedRoles":{"type":"array","items":{"type":"string","enum":["coordinator","referee"]}},"presets":{"type":"array","maxItems":30,"items":{"type":"object","required":["id","name","payload"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"payload":{}}}}}}}}},"responses":{"201":{"description":"Registry containing packets with compiled schemas and per-packet canSend flags, plus canManage and supported placeholders."},"400":{"description":"Invalid definition, missing token, or unavailable overlay."},"401":{"description":"Authentication is required."},"403":{"description":"Only organisers, admins, staff, stream hosts and globally permitted users can configure custom packets."},"409":{"description":"Packet type already registered on this overlay."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}/custom-packets/{packetGuid}":{"put":{"summary":"Update a custom packet definition and its permissions","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"packetGuid","required":true,"schema":{"type":"string"}}],"description":"Hosts and tournament management can configure packets. The payload type is a restricted JSON-compatible TypeScript type, parsed without execution. Extra keys are preserved when sending.\n\nRequired token scope: `rest:write`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["packetType","name","schemaSource","allowedRoles","presets"],"properties":{"packetType":{"type":"string","maxLength":80},"name":{"type":"string","maxLength":120},"description":{"type":"string","maxLength":2000},"schemaSource":{"type":"string","maxLength":10000,"example":"{ category: string; }"},"allowedRoles":{"type":"array","items":{"type":"string","enum":["coordinator","referee"]}},"presets":{"type":"array","maxItems":30,"items":{"type":"object","required":["id","name","payload"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"payload":{}}}}}}}}},"responses":{"200":{"description":"Registry containing packets with compiled schemas and per-packet canSend flags, plus canManage and supported placeholders."},"400":{"description":"Invalid definition, missing token, or unavailable overlay."},"401":{"description":"Authentication is required."},"403":{"description":"Only organisers, admins, staff, stream hosts and globally permitted users can configure custom packets."},"404":{"description":"Packet not found on this overlay."},"409":{"description":"Packet type already registered on this overlay."}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Remove a custom packet definition","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"packetGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registry containing packets with compiled schemas and per-packet canSend flags, plus canManage and supported placeholders."},"400":{"description":"Invalid definition, missing token, or unavailable overlay."},"401":{"description":"Authentication is required."},"403":{"description":"Only organisers, admins, staff, stream hosts and globally permitted users can configure custom packets."},"404":{"description":"Packet not found on this overlay."}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}/custom-states":{"get":{"summary":"List saved states the operator can update","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"description":"Extra JSON keys are preserved. Replacing values, editing definitions and deleting require the current revision. Merge mode recursively merges objects and replaces arrays or primitives; the entire result must satisfy the registered schema. Only configured writers may update values; hosts and management may configure states.\n\nRequired token scope: `rest:read`.","responses":{"200":{"description":"Registry with compiled schemas, current values, revisions and canUpdate permissions."},"400":{"description":"Invalid schema or value."},"401":{"description":"Authentication required."},"403":{"description":"Insufficient permissions."},"404":{"description":"State not found on this overlay."},"409":{"description":"Revision conflict or duplicate state key."}},"x-required-scopes":["rest:read"]},"post":{"summary":"Register a saved state with an initial JSON value","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["stateKey","name","schemaSource","allowedRoles","presets","value"],"properties":{"stateKey":{"type":"string","maxLength":80},"name":{"type":"string","maxLength":120},"schemaSource":{"type":"string","maxLength":10000},"description":{"type":"string"},"allowedRoles":{"type":"array","items":{"type":"string","enum":["coordinator","referee"]}},"presets":{"type":"array","items":{"type":"object"}},"value":{}}}}}},"description":"Extra JSON keys are preserved. Replacing values, editing definitions and deleting require the current revision. Merge mode recursively merges objects and replaces arrays or primitives; the entire result must satisfy the registered schema. Only configured writers may update values; hosts and management may configure states.\n\nRequired token scope: `rest:write`.","responses":{"201":{"description":"Registry with compiled schemas, current values, revisions and canUpdate permissions."},"400":{"description":"Invalid schema or value."},"401":{"description":"Authentication required."},"403":{"description":"Insufficient permissions."},"404":{"description":"State not found on this overlay."},"409":{"description":"Revision conflict or duplicate state key."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}/custom-states/{stateGuid}":{"put":{"summary":"Edit the schema and permissions of a saved state","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"stateGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["stateKey","name","schemaSource","allowedRoles","presets","expectedRevision"],"properties":{"stateKey":{"type":"string","maxLength":80},"name":{"type":"string","maxLength":120},"schemaSource":{"type":"string","maxLength":10000},"description":{"type":"string"},"allowedRoles":{"type":"array","items":{"type":"string","enum":["coordinator","referee"]}},"presets":{"type":"array","items":{"type":"object"}},"value":{},"expectedRevision":{"type":"integer","minimum":1}}}}}},"description":"Extra JSON keys are preserved. Replacing values, editing definitions and deleting require the current revision. Merge mode recursively merges objects and replaces arrays or primitives; the entire result must satisfy the registered schema. Only configured writers may update values; hosts and management may configure states.\n\nRequired token scope: `rest:write`.","responses":{"200":{"description":"Registry with compiled schemas, current values, revisions and canUpdate permissions."},"400":{"description":"Invalid schema or value."},"401":{"description":"Authentication required."},"403":{"description":"Insufficient permissions."},"404":{"description":"State not found on this overlay."},"409":{"description":"Revision conflict or duplicate state key."}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Remove a saved state","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"stateGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expectedRevision"],"properties":{"expectedRevision":{"type":"integer","minimum":1}}}}}},"description":"Extra JSON keys are preserved. Replacing values, editing definitions and deleting require the current revision. Merge mode recursively merges objects and replaces arrays or primitives; the entire result must satisfy the registered schema. Only configured writers may update values; hosts and management may configure states.\n\nRequired token scope: `rest:write`.","responses":{"200":{"description":"Registry with compiled schemas, current values, revisions and canUpdate permissions."},"400":{"description":"Invalid schema or value."},"401":{"description":"Authentication required."},"403":{"description":"Insufficient permissions."},"404":{"description":"State not found on this overlay."},"409":{"description":"Revision conflict or duplicate state key."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}/custom-states/{stateGuid}/value":{"patch":{"summary":"Merge or replace a saved JSON value","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"stateGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["merge","replace"]},"value":{},"expectedRevision":{"type":"integer","minimum":1},"presetId":{"type":"string"}}}}}},"description":"Extra JSON keys are preserved. Replacing values, editing definitions and deleting require the current revision. Merge mode recursively merges objects and replaces arrays or primitives; the entire result must satisfy the registered schema. Only configured writers may update values; hosts and management may configure states.\n\nRequired token scope: `rest:write`.","responses":{"200":{"description":"Saved value and revision."},"400":{"description":"Invalid schema or value."},"401":{"description":"Authentication required."},"403":{"description":"Insufficient permissions."},"404":{"description":"State not found on this overlay."},"409":{"description":"Revision conflict or duplicate state key."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/overlays/{overlayGuid}/custom-states/values":{"get":{"summary":"Fetch all saved JSON values for a custom overlay","tags":["Overlays"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"overlayGuid","required":true,"schema":{"type":"string"}}],"description":"Overlay readers may fetch snapshots without write permissions. Returns state keys, values, identities, revisions and update metadata; editing permissions and presets are not exposed.\n\nRequired token scope: `rest:read`.","responses":{"200":{"description":"Overlay GUID and current states array."},"400":{"description":"Overlay not found or invalid scope."},"401":{"description":"Authentication required."},"403":{"description":"This user cannot read the overlay."}},"x-required-scopes":["rest:read"]}},"/oauth/applications":{"get":{"summary":"List OAuth applications visible to the current developer","description":"Requires `rest:user:read`. Owners and developers can request `all=true`; other users receive only their applications.\n\nRequired token scope: `rest:user:read`.","tags":["OAuth Applications"],"security":[{"BeatKhanaAuth":["rest:user:read"]}],"parameters":[{"in":"query","name":"all","schema":{"type":"boolean"},"description":"Owner/developer-only all-applications view."}],"responses":{"200":{"description":"Applications with secret hashes omitted."},"401":{"description":"Missing or invalid BeatKhana token."},"403":{"description":"Token scope or global developer access is insufficient."}},"x-required-scopes":["rest:user:read"]},"post":{"summary":"Create an OAuth application","description":"Requires `rest:user:write` plus `global:oauth:apps:create`. Name must contain only 1-50 ASCII letters/numbers. Redirects are exact and must use HTTPS except localhost, 127.0.0.1, ::1, or .local. The plaintext secret is returned once.\n\nRequired token scope: `rest:user:write`.","tags":["OAuth Applications"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","scopes","redirectUris"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9]{1,50}$"},"imageUrl":{"type":"string","nullable":true,"description":"URL returned by the BeatKhana CDN upload endpoint."},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/OAuthScope"}},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"},"description":"HTTPS except localhost","127.0.0.1":null,"::1":null,"and .local development hosts.":null,"minItems":1}}}}}},"responses":{"201":{"description":"Application and one-time clientSecret."},"400":{"description":"Invalid metadata.","scope":null,"or redirect URI.":null},"401":{"description":"Invalid token."},"403":{"description":"Creation disabled or scope missing."}},"parameters":[],"x-required-scopes":["rest:user:write"]}},"/oauth/applications/{guid}":{"get":{"summary":"Get one OAuth application","description":"Requires `rest:user:read` and application ownership; role:owner and role:dev can inspect any application.\n\nRequired token scope: `rest:user:read`.","tags":["OAuth Applications"],"security":[{"BeatKhanaAuth":["rest:user:read"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Application metadata."},"403":{"description":"Not an owner or platform developer."},"404":{"description":"Application not found."}},"x-required-scopes":["rest:user:read"]},"patch":{"summary":"Edit an OAuth application","description":"Requires `rest:user:write` and ownership, unless the user has role:owner or role:dev. Validates name, requested scopes, and every exact redirect URI. Built-in system applications are immutable; custom protocol redirects are reserved for the Dynamic Overlay client.\n\nRequired token scope: `rest:user:write`.","tags":["OAuth Applications"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated application without secret hash."},"400":{"description":"Invalid metadata."},"403":{"description":"Not authorized or the target is an immutable system application."},"404":{"description":"Not found."}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9]{1,50}$"},"imageUrl":{"type":"string","nullable":true},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/OAuthScope"}},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}}}}}}},"x-required-scopes":["rest:user:write"]},"delete":{"summary":"Delete an OAuth application","description":"Requires `rest:user:write` and ownership, role:owner, or role:dev. Cascades to pending requests, authorization codes, and refresh tokens. Built-in system applications cannot be deleted.\n\nRequired token scope: `rest:user:write`.","tags":["OAuth Applications"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Deleted and grants revoked."},"403":{"description":"Not authorized."},"404":{"description":"Not found."}},"x-required-scopes":["rest:user:write"]}},"/oauth/applications/{guid}/secret":{"post":{"summary":"Rotate an OAuth client secret","description":"Requires `rest:user:write`. The new plaintext secret is shown exactly once. Public built-in clients do not have rotatable secrets.\n\nRequired token scope: `rest:user:write`.","tags":["OAuth Applications"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"One-time clientSecret."},"403":{"description":"Not authorized."},"404":{"description":"Application not found."}},"x-required-scopes":["rest:user:write"]}},"/oauth/authorize":{"get":{"summary":"Start OAuth 2.0 authorization code login","description":"Validates client, exact redirect URI, requested scopes, state, and PKCE. Third-party applications are sent to BeatKhana's authenticated consent page first, which reuses an existing BeatKhana session and only starts Discord login when necessary. The BeatKhana website login and built-in Dynamic Overlay remain direct identity-login clients. The Dynamic Overlay is public and requires S256 PKCE.\n\nRequired token scope: `rest:read`.","tags":["OAuth 2.0"],"parameters":[{"in":"query","name":"response_type","required":true,"schema":{"type":"string","enum":["code"]}},{"in":"query","name":"client_id","required":true,"schema":{"type":"string"}},{"in":"query","name":"redirect_uri","required":true,"schema":{"type":"string","format":"uri"}},{"in":"query","name":"scope","required":true,"schema":{"type":"string"}},{"in":"query","name":"state","required":true,"schema":{"type":"string"}},{"in":"query","name":"code_challenge","schema":{"type":"string"}},{"in":"query","name":"code_challenge_method","schema":{"type":"string","enum":["S256"]}}],"responses":{"302":{"description":"Redirect to BeatKhana consent","or directly to Discord for a built-in identity-login client.":null},"400":{"description":"Invalid OAuth request."},"503":{"description":"Direct Discord OAuth is not configured for an identity-login client."}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/auth/discord/callback":{"get":{"summary":"Complete API-owned Discord authorization","description":"Exchanges Discord's short-lived code server-to-server, validates the stored OAuth state, and creates/synchronizes the BeatKhana and Authentik users. The trusted `beatkhana-web` first-party client is approved automatically; BSEUC and third-party applications continue to the BeatKhana consent screen. Browser referrer headers are not trusted.","tags":["Authentication"],"parameters":[{"in":"query","name":"code","required":true,"schema":{"type":"string"}},{"in":"query","name":"state","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to BeatKhana consent after synchronizing the BeatKhana and Authentik accounts."},"400":{"description":"Invalid or expired state."},"502":{"description":"Discord exchange failed."},"503":{"description":"Direct Discord OAuth is not configured."}},"security":[{"BeatKhanaAuth":[]}]}},"/oauth/authorize/session":{"post":{"summary":"Attach an authenticated BeatKhana user to a pending OAuth request","description":"Requires `rest:write`. Used by the protected BeatKhana consent page after `requireAuth()` has reused or established the user's BeatKhana session. Returns the validated application identity and requested scopes.\n\nRequired token scope: `rest:write`.","tags":["OAuth 2.0"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requestToken"],"properties":{"requestToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Application identity and requested scope descriptions."},"401":{"description":"Missing or invalid BeatKhana session."},"404":{"description":"Request expired or invalid."},"409":{"description":"Request already belongs to another user."}},"parameters":[],"x-required-scopes":["rest:write"]}},"/oauth/authorize/request/{requestToken}":{"get":{"summary":"Read a pending consent request","description":"Requires `rest:read`. Reads application and scope details only when the pending request belongs to the authenticated user.\n\nRequired token scope: `rest:read`.","tags":["OAuth 2.0"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"requestToken","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Application identity and exact requested scope descriptions."},"404":{"description":"Request expired or invalid."}},"x-required-scopes":["rest:read"]}},"/oauth/authorize/approve":{"post":{"summary":"Approve a pending OAuth request","description":"Requires `rest:write`. Called server-to-server by the consent website after an approval navigation. Consumes a request belonging to the authenticated user and creates a single-use authorization code valid for ten minutes. The website immediately redirects the browser to `redirect_uri` with `code` and the original `state` in its query string.","tags":["OAuth 2.0"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requestToken"],"properties":{"requestToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Exact validated redirect URL in redirectTo."},"404":{"description":"Request expired or invalid."}},"parameters":[]}},"/oauth/authorize/deny":{"post":{"summary":"Deny a pending OAuth request","description":"Requires `rest:write`. Called server-to-server by the consent website after a denial navigation. Consumes a request belonging to the authenticated user and returns a validated redirect containing OAuth `access_denied` and the original state.","tags":["OAuth 2.0"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requestToken"],"properties":{"requestToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Validated redirect URL in redirectTo."},"404":{"description":"Request expired or invalid."}},"parameters":[]}},"/oauth/userinfo":{"get":{"summary":"Read OAuth user information","description":"Returns an OpenID Connect-compatible profile for the bearer token's BeatKhana user. Requires `rest:user:read`; it is intended for OAuth clients such as Authentik and never exposes linked provider tokens.\n\nRequired token scope: `rest:read`.","tags":["OAuth 2.0"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Stable subject","username":null,"display name":null,"avatar":null,"and Discord identifier.":null},"401":{"description":"Missing","invalid":null,"or expired bearer token.":null},"404":{"description":"Token user no longer exists."}},"parameters":[],"x-required-scopes":["rest:read"]}},"/oauth/token":{"post":{"summary":"Exchange an authorization code or refresh token","description":"OAuth 2.0 token endpoint. Confidential clients authenticate with HTTP Basic (recommended) or client_id/client_secret form fields. The built-in Dynamic Overlay public client supplies client_id and S256 PKCE instead of a secret. Access tokens last 60 days; refresh tokens rotate on every use.","tags":["OAuth 2.0"],"security":[{"OAuthClient":[]}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["grant_type"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"code":{"type":"string"},"redirect_uri":{"type":"string","format":"uri"},"code_verifier":{"type":"string"},"refresh_token":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string","format":"password"}}}}}},"responses":{"200":{"description":"Bearer access token","expiry":null,"rotated refresh token":null,"and granted scope.":null},"400":{"description":"Invalid grant."},"401":{"description":"Invalid client."}},"parameters":[]}},"/oauth/revoke":{"post":{"summary":"Revoke an OAuth refresh token","description":"RFC 7009-style revocation. Confidential clients authenticate with their secret; the built-in Dynamic Overlay public client uses its client_id. The response is 200 even when the token is unknown.","tags":["OAuth 2.0"],"security":[{"OAuthClient":[]}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"type":"string","format":"password"}}}}}},"responses":{"200":{"description":"Revocation processed."},"401":{"description":"Invalid client."}},"parameters":[]}},"/tournaments/{tournamentIdentifier}/matches":{"get":{"summary":"Get all matches with full hierarchy","description":"Returns the complete tournament structure with all divisions, brackets, and matches with full details. This hierarchy is public, matching the public tournament endpoint when `matches=true` is requested.","tags":["Matches"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"responses":{"200":{"description":"Match hierarchy retrieved successfully","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Missing tournament identifier"},"404":{"description":"The tournament could not be found"},"500":{"description":"Match hierarchy retrieval failed"}}},"post":{"summary":"Create a new match","description":"Creates a new match in a specified bracket. bracketGuid is required in request body.\n\nRequired token scope: `rest:write`.","tags":["Matches"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bracketGuid"],"properties":{"bracketGuid":{"type":"string","description":"GUID of the bracket this match belongs to"},"scheduledTime":{"type":"number"},"matchNumber":{"type":"integer"},"matchType":{"type":"string"},"nextMatchGuid":{"type":"string"},"loserMatchGuid":{"type":"string"},"pool":{"type":"string"}}}}}},"responses":{"200":{"description":"Match created successfully"},"400":{"description":"Bad request - missing or invalid data"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/matches/{matchGuid}":{"put":{"summary":"Update match details (partial updates supported)","description":"Updates a match. Supports partial updates - only include fields to change. Allows bracket reassignment.\n\nRequired token scope: `rest:write`.","tags":["Matches"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"matchGuid"}],"requestBody":{"required":true},"responses":{"200":{"description":"Match updated successfully"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a match","description":"Permanently deletes a match and its results\n\nRequired token scope: `rest:write`.","tags":["Matches"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"matchGuid"}],"responses":{"200":{"description":"Match deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/matches/bulk":{"patch":{"summary":"Update several matches at once","description":"Applies the same values to every listed match in one transaction. Only fields present in `update` change; all other match data is kept. Supported fields are poolGuids, maxScore, vodLink, matchType, picksBansFlowGuid, the assign*Enabled switches and the assigned staff lists. Staff lists replace the current assignments unless `staffMode` is `add`. Bracket links, teams, times and status stay per-match.\n\nRequired token scope: `rest:write`.","tags":["Matches"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["matchGuids","update"],"properties":{"matchGuids":{"type":"array","items":{"type":"string"}},"staffMode":{"type":"string","enum":["replace","add"]},"update":{"type":"object"}}}}}},"responses":{"200":{"description":"Every listed match was updated"},"400":{"description":"Invalid values, or a match cannot take the change (for example a flow change after picks and bans started)"},"401":{"description":"Unauthorized"},"404":{"description":"A listed match does not belong to this tournament"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/match-settings":{"get":{"summary":"Get a tournament's match settings","description":"Coordinator permissions and the scoring rule every match in the tournament uses. Public.","tags":["Matches"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The tournament's match settings, with defaults for anything never changed."},"404":{"description":"The tournament could not be found"}}},"put":{"summary":"PUT /tournaments/{tournamentIdentifier}/match-settings","description":"Auto-documented from src/routes/matches.route.ts.\n\nRequired token scope: `rest:write`.","tags":["matches"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/match-settings/casters-channels":{"get":{"summary":"List voice channels for the casters channel","description":"The voice and stage channels of the tournament's linked Discord server, for choosing the casters channel in the match settings. The BeatKhana bot joins that channel while anyone is in it and shows overlays who of the casters is speaking. Same permissions as changing the match settings.\n\nRequired token scope: `rest:read`.","tags":["Matches"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Whether a Discord server is linked, and its voice channels in Discord's order.","content":{"application/json":{"schema":{"type":"object","properties":{"serverLinked":{"type":"boolean"},"channels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string","nullable":true}}}}}}}}},"401":{"description":"Missing or invalid authorization, or insufficient permissions"},"404":{"description":"The tournament could not be found"},"502":{"description":"The bot could not read the linked Discord server"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/match-settings/match-channels":{"get":{"summary":"List text channels for the current match channel","description":"The text and announcement channels of the tournament's linked Discord server, for choosing the current match channel in the match settings. The BeatKhana bot posts results, score decisions and timeouts there. Same permissions as changing the match settings.\n\nRequired token scope: `rest:read`.","tags":["Matches"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Whether a Discord server is linked, and its text channels in Discord's order.","content":{"application/json":{"schema":{"type":"object","properties":{"serverLinked":{"type":"boolean"},"channels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"type":"string","nullable":true}}}}}}}}},"401":{"description":"Missing or invalid authorization, or insufficient permissions"},"404":{"description":"The tournament could not be found"},"502":{"description":"The bot could not read the linked Discord server"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps":{"post":{"summary":"Add a map to a tournament pool","description":"Adds a Beat Saber map from BeatSaver to a specific tournament pool. The map will be analyzed and stored with its metadata. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool to add the map to"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["identifier","characteristic","difficulty"],"properties":{"identifier":{"type":"string","description":"BeatSaver map key or hash","example":"1a2b3"},"characteristic":{"type":"string","enum":["Standard","Lawless","Lightshow","Legacy"],"description":"Map characteristic"},"difficulty":{"type":"string","enum":["Easy","Normal","Hard","Expert","ExpertPlus"],"description":"Map difficulty"},"modifiers":{"type":"array","description":"Gameplay modifiers for this map. No Fail is not allowed; it is a tournament default.","items":{"type":"string","enum":["IF","4L","NB","NW","NA","DA","GN","SS","FS","SFS","SA","PM","SN","ZM"]}}}}}}},"responses":{"200":{"description":"Map added successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully added the map to the pool!"},"newMap":{"type":"object"}}}}}},"400":{"description":"Bad request - missing parameters or invalid difficulty","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament, pool, or map not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}},"500":{"description":"Internal server error during map analysis or insertion","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to analyze map"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps/{mapGuid}":{"delete":{"summary":"Remove a map from a tournament pool","description":"Permanently removes a map from a specific tournament pool. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool"},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map to remove"}],"responses":{"200":{"description":"Map removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed the map from the pool!"}}}}}},"400":{"description":"Bad request - missing authorization header or parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament or pool not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The targeted map pool does not exist!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps/{mapGuid}/flair":{"put":{"summary":"Set or update a map's flair","description":"Assigns a flair to a map in a tournament pool. Flairs are visual indicators or categories for maps. Set flairGuid to null to remove the flair.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool"},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map to update"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["flairGuid"],"properties":{"flairGuid":{"type":"string","nullable":true,"description":"The GUID of the flair to assign, or null to remove flair","example":"abc-123-def-456"}}}}}},"responses":{"200":{"description":"Map flair updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully modified the flair of the map!"}}}}}},"400":{"description":"Bad request - missing authorization header or parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the flair guid!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament, pool, map, or flair not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The targeted flair does not exist!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/maps/{mapGuid}/modifiers":{"put":{"summary":"Set the gameplay modifiers of a pool map or suggestion","description":"Map poolers and above. The tournament's default modifiers (match settings) are applied on top. No Fail is a tournament default and cannot be set per map.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["modifiers"],"properties":{"modifiers":{"type":"array","items":{"type":"string","enum":["IF","4L","NB","NW","NA","DA","GN","SS","FS","SFS","SA","PM","SN","ZM"]}}}}}}},"responses":{"200":{"description":"The saved modifiers"},"400":{"description":"Invalid modifiers"},"401":{"description":"Missing authorization or insufficient permissions"},"404":{"description":"The tournament or map could not be found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/maps/{mapGuid}/comments":{"post":{"summary":"Add a comment to any tournament map","description":"Generic comment endpoint used by pool maps and suggestion-only maps.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"type":"string"},"isVote":{"type":"boolean"},"voteDirection":{"type":"string","enum":["up","down"],"nullable":true}}}}}},"responses":{"200":{"description":"Comment added successfully"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/maps/{mapGuid}/comments/{commentGuid}":{"delete":{"summary":"Delete a map comment","description":"Deletes a generic map comment used by pool maps and suggestion-only maps.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"commentGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Comment deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Comment not found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps/{mapGuid}/comments":{"post":{"summary":"Add a comment or vote to a map","description":"Add a comment to a map in a tournament pool. Comments can optionally be votes. Users can only vote once per map.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool"},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map to comment on"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["comment","isVote"],"properties":{"comment":{"type":"string","description":"The comment text","example":"Great map for the finals!"},"isVote":{"type":"boolean","description":"Whether this comment is a vote","example":false},"voteDirection":{"type":"string","description":"Vote direction if isVote is true","enum":["up","down"],"example":"up"}}}}}},"responses":{"200":{"description":"Comment added successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully posted comment!"},"comment":{"type":"object"}}}}}},"400":{"description":"Bad request - missing parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the comment content!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or duplicate vote","content":{"text/plain":{"schema":{"type":"string"},"example":"You are not allowed to vote on a map twice. Delete the comment with the vote!"}}},"404":{"description":"Tournament, pool, or map not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The targeted map does not exist!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps/{mapGuid}/comments/{commentGuid}":{"delete":{"summary":"Remove a comment from a map","description":"Delete a comment that the user previously posted on a map. Users can only delete their own comments.\n\nRequired token scope: `rest:write`.","tags":["Maps"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool"},{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map"},{"in":"path","name":"commentGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the comment to delete"}],"responses":{"200":{"description":"Comment removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed comment!"}}}}}},"400":{"description":"Bad request - missing authorization header or parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide the comment guid!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"404":{"description":"Tournament, pool, map, or comment not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The targeted comment does not exist!"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps/fromFile":{"post":{"summary":"Create a map pool from a .bplist or .json file","description":"Creates a new map pool for a tournament by importing maps from a BeatSaber playlist file. The pool image is uploaded to CDN internally. Requires pooler permissions or higher.\n\nRequired token scope: `rest:write`.","tags":["Maps","Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["playlistTitle","songs"],"properties":{"playlistTitle":{"type":"string","description":"Name of the map pool"},"playlistAuthor":{"type":"string","description":"Author of the playlist"},"playlistDescription":{"type":"string","description":"Description of the playlist"},"image":{"type":"string","description":"Base64 encoded image string (optional)"},"songs":{"type":"array","description":"Array of maps to add to the pool","items":{"type":"object","required":["hash","key","characteristic","difficulty"],"properties":{"hash":{"type":"string"},"key":{"type":"string"},"characteristic":{"type":"string","enum":["Standard","Lawless","Lightshow","Legacy"]},"difficulty":{"type":"string","enum":["Easy","Normal","Hard","Expert","ExpertPlus"]}}}}}}}}},"responses":{"200":{"description":"Pool created with all maps imported successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string"},"poolGuid":{"type":"string"},"imageUrl":{"type":"string"},"mapsAdded":{"type":"number"}}}}}},"400":{"description":"Bad request - missing required fields"},"401":{"description":"Unauthorized - invalid token or insufficient permissions"},"404":{"description":"Tournament not found"},"500":{"description":"Internal server error"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/maps/import":{"post":{"summary":"Import maps into an existing tournament pool from playlist data","description":"Imports Beat Saber maps from playlist song data into an existing tournament pool. The request only needs the map data; the pool and image should be created separately.\n\nRequired token scope: `rest:write`.","tags":["Maps","Pools"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the map pool to import maps into"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["songs"],"properties":{"songs":{"type":"array","items":{"type":"object","properties":{"hash":{"type":"string"},"levelid":{"type":"string"},"difficulties":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","enum":["Easy","Normal","Hard","Expert","ExpertPlus"]},"characteristic":{"type":"string","enum":["Standard","Lawless","Lightshow","Legacy"]}}}}}}}}}}}},"responses":{"200":{"description":"Maps imported successfully"},"400":{"description":"Bad request - missing songs or invalid playlist data"},"401":{"description":"Unauthorized - invalid token or insufficient permissions"},"404":{"description":"Tournament, pool, or map not found"},"500":{"description":"Internal server error"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/mapPoolSettings":{"get":{"summary":"Get map pool settings","tags":["Map Pooling"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tournament map pool settings"},"404":{"description":"Tournament not found"}}},"put":{"summary":"Update map pool settings","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"allowMapSuggestions":{"type":"boolean"},"hidePublicSuggestionsFromUsers":{"type":"boolean"},"showRecommendationsFromPreviousTournaments":{"type":"boolean"},"archivePublishedPoolsWhenConcluded":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Map pool settings updated"},"401":{"description":"Unauthorized"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapPoolingWorkspace":{"get":{"summary":"Get the map pooling workspace","description":"Returns map pool settings, pools, suggestions, and archived pools for poolers.\n\nRequired token scope: `rest:read`.","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Map pooling workspace data"},"401":{"description":"Unauthorized"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/details":{"put":{"summary":"Update map pool details","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"imageUrl":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Map pool details updated"},"404":{"description":"Map pool not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/configuration":{"put":{"summary":"Update map pool configuration","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"targetType":{"type":"string","enum":["qualifiers","best_of","custom"]},"targetBestOf":{"type":"number","nullable":true},"targetMapCount":{"type":"number","nullable":true},"divisionGuid":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Pool configuration updated"},"404":{"description":"Map pool not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/slots":{"put":{"summary":"Replace map pool slots","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"slots":{"type":"array","items":{"type":"object","properties":{"guid":{"type":"string","description":"Existing slot GUID. Omit when adding a new slot."},"flairGuid":{"type":"string","nullable":true},"label":{"type":"string","nullable":true},"sequence":{"type":"number"}}}}}}}}},"responses":{"200":{"description":"Pool slots updated"},"404":{"description":"Pool configuration not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapSuggestions":{"get":{"summary":"List tournament map suggestions","description":"Public users can see public suggestions when enabled; poolers can request the pooler view.","tags":["Map Pooling"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"query","name":"scope","schema":{"type":"string","enum":["pooler"]}},{"in":"query","name":"source","schema":{"type":"string","enum":["public","pooler"]}},{"in":"query","name":"mine","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Map suggestions"},"401":{"description":"Unauthorized for pooler suggestions"}}},"post":{"summary":"Create a map suggestion","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","characteristic","difficulty"],"properties":{"key":{"type":"string"},"characteristic":{"type":"string"},"difficulty":{"type":"string"},"source":{"type":"string","enum":["public","pooler"]},"modifiers":{"type":"array","description":"Gameplay modifiers for this map. No Fail is not allowed; it is a tournament default.","items":{"type":"string","enum":["IF","4L","NB","NW","NA","DA","GN","SS","FS","SFS","SA","PM","SN","ZM"]}},"flairGuid":{"type":"string","nullable":true},"targetPoolGuid":{"type":"string","nullable":true},"targetDivisionGuid":{"type":"string","nullable":true},"targetType":{"type":"string","nullable":true},"targetBestOf":{"type":"number","nullable":true},"initialVote":{"type":"string","enum":["no","good"],"nullable":true}}}}}},"responses":{"200":{"description":"Map suggestion created"},"409":{"description":"Duplicate suggestion"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapSuggestions/batch":{"post":{"summary":"Create multiple map suggestions","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["suggestions"],"properties":{"suggestions":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"characteristic":{"type":"string"},"difficulty":{"type":"string"},"source":{"type":"string","enum":["public","pooler"]},"modifiers":{"type":"array","items":{"type":"string","enum":["IF","4L","NB","NW","NA","DA","GN","SS","FS","SFS","SA","PM","SN","ZM"]}},"flairGuid":{"type":"string","nullable":true},"initialVote":{"type":"string","enum":["no","good"],"nullable":true}}}}}}}}},"responses":{"200":{"description":"Batch suggestion result with created suggestions and per-item errors"},"400":{"description":"No suggestions provided"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapSuggestions/{suggestionGuid}/promote":{"post":{"summary":"Promote a public suggestion to the pooler list","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"suggestionGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"targetPoolGuid":{"type":"string","nullable":true},"targetDivisionGuid":{"type":"string","nullable":true},"targetType":{"type":"string","nullable":true},"targetBestOf":{"type":"number","nullable":true},"flairGuid":{"type":"string","nullable":true},"initialVote":{"type":"string","enum":["no","good"],"nullable":true}}}}}},"responses":{"200":{"description":"Suggestion promoted to pooler list"},"404":{"description":"Suggestion not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapSuggestions/{suggestionGuid}/vote":{"post":{"summary":"Vote on a map suggestion","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"suggestionGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["vote"],"properties":{"vote":{"type":"string","enum":["no","good"]}}}}}},"responses":{"200":{"description":"Vote saved"},"400":{"description":"Invalid vote"},"404":{"description":"Suggestion not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Remove the current pooler's vote from a suggested map","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"suggestionGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Vote removed from every copy of the suggested map"},"404":{"description":"Suggestion not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapSuggestions/{suggestionGuid}":{"delete":{"summary":"Remove a map suggestion","description":"Removes a public (user) or internal (pooler) suggestion and its votes. Head poolers, organisers and admins may remove\nany suggestion; everyone else only the ones they suggested. Maps already added to a pool stay there, and other\nsuggestions of the same map are not affected.\n\n\nRequired token scope: `rest:write`.","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"suggestionGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Suggestion removed"},"403":{"description":"Only head poolers can remove other people's suggestions"},"404":{"description":"Suggestion not found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/useSuggestions":{"post":{"summary":"Add multiple suggestions to a map pool","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"suggestions":{"type":"array","items":{"type":"object","required":["suggestionGuid"],"properties":{"suggestionGuid":{"type":"string"},"flairGuid":{"type":"string","nullable":true},"slotGuid":{"type":"string","nullable":true},"sequence":{"type":"number"}}}}}}}}},"responses":{"200":{"description":"Suggestions added to the pool with per-item errors"},"404":{"description":"Map pool not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/useSuggestion/{suggestionGuid}":{"post":{"summary":"Add one suggestion to a map pool","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"suggestionGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"flairGuid":{"type":"string","nullable":true},"slotGuid":{"type":"string","nullable":true},"sequence":{"type":"number"}}}}}},"responses":{"200":{"description":"Suggestion added to the pool"},"404":{"description":"Map pool or suggestion not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/mapPooling/archiveConcluded":{"post":{"summary":"Archive map pools for a concluded tournament","tags":["Map Pooling"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Tournament map pools archived"},"400":{"description":"Tournament is not completed"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/resources/pooling/pools":{"get":{"summary":"List public archived map pools","tags":["Pooling Resources"],"responses":{"200":{"description":"Archived public map pools"}},"parameters":[]}},"/resources/pooling/pools/{poolGuid}":{"get":{"summary":"Get a public archived map pool","tags":["Pooling Resources"],"parameters":[{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Archived public map pool"},"404":{"description":"Archived pool not found"}}}},"/resources/pooling/maps":{"get":{"summary":"List archived map appearances","tags":["Pooling Resources"],"responses":{"200":{"description":"Archived map appearances across public pools"}},"parameters":[]}},"/resources/pooling/maps/{mapGuid}":{"get":{"summary":"Get archived appearances for a map","tags":["Pooling Resources"],"parameters":[{"in":"path","name":"mapGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Archived map with appearances"},"404":{"description":"Archived map not found"}}}},"/tournaments/{tournamentIdentifier}/pools/{poolGuid}/playlist":{"get":{"summary":"Download a map pool as a Beat Saber playlist","description":"Returns the pool as a `.bplist` playlist (JSON). Published pools are public and include `customData.syncURL`, which points to this endpoint so playlist mods can keep the pool up to date. Draft pools require pooler permissions and omit the sync URL. Add `download=1` to receive an attachment.\n\nRequired token scope: `rest:read`.","tags":["Map Pooling"],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"poolGuid","required":true,"schema":{"type":"string"}},{"in":"query","name":"download","required":false,"schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"Beat Saber playlist","content":{"application/json":{"schema":{"type":"object","properties":{"playlistTitle":{"type":"string"},"playlistAuthor":{"type":"string"},"image":{"type":"string","description":"Raw base64 cover image"},"customData":{"type":"object","properties":{"syncURL":{"type":"string"}}},"songs":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Draft pools require pooler permissions"},"404":{"description":"Tournament or pool not found"}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/analyseMap/{mapIdentifier}/{diff}":{"get":{"summary":"Analyze a Beat Saber map","description":"Performs detailed analysis on a supported Beat Saber map characteristic from BeatSaver using either a map key or hash. Requires specific global permissions to access.\n\nRequired token scope: `rest:read`.","tags":["Map Analysis"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"mapIdentifier","required":true,"schema":{"type":"string"},"description":"BeatSaver map key (e.g., \"1a2b3\") or hash (e.g., \"abc123def456...\")"},{"in":"path","name":"diff","required":true,"schema":{"type":"string","enum":["Easy","Normal","Hard","Expert","ExpertPlus"]},"description":"The difficulty level to analyze"},{"in":"query","name":"characteristic","required":false,"schema":{"type":"string","enum":["Standard","Lawless","Lightshow","Legacy"],"default":"Standard"},"description":"The Beat Saber map characteristic to analyze"}],"responses":{"200":{"description":"Map analysis completed successfully","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Bad request - missing authorization header or difficulty doesn't exist","content":{"text/plain":{"schema":{"type":"string"},"example":"The specified difficulty 'ExpertPlus' does not exist for this map"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"You are not permitted to perform this action. Insufficient global permissions attached to the user."}}},"404":{"description":"Map not found on BeatSaver","content":{"text/plain":{"schema":{"type":"string"},"example":"Map not found on BeatSaver"}}},"500":{"description":"Internal server error during map analysis","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to analyze map"}}}},"x-required-scopes":["rest:read"]}},"/tournaments":{"post":{"summary":"Create a new tournament","description":"Create an empty tournament object with pre defined values with the owner set to the caller of the method. Users can have a maximum of 2 active tournaments unless they have admin permissions. Email verification is required.\n\nRequired token scope: `rest:write`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","id"],"properties":{"name":{"type":"string","pattern":"^[a-zA-Z0-9 ]{1,50}$"},"id":{"type":"string","pattern":"^[a-zA-Z0-9]{1,25}$"}}}}}},"responses":{"200":{"description":"Tournament created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Successfully created tournament: \"My Tournament\" with the id: \"mytourney\""}}}}}},"400":{"description":"Bad request - invalid input, tournament ID already exists, or user has too many active tournaments","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or email not verified","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! Please request a new token!"}}},"402":{"description":"Tournament name or ID not specified","content":{"text/plain":{"schema":{"type":"string"},"example":"402: Tournament name not specified"}}},"500":{"description":"Internal server error during token validation","content":{"text/plain":{"schema":{"type":"string"},"example":"Oops, something went wrong during token validation! Please try again."}}}},"parameters":[],"x-required-scopes":["rest:write"]},"get":{"summary":"Get all approved and active tournaments","description":"Retrieves all tournaments with approved visibility status that are not cancelled, draft, or completed.\nIf an authorization token is optionally provided, any additional tournaments that are in a\ndraft or cancelled status (regardless of visibility status) where the authenticated user holds\nat least one tournament-level permission are also included in the response. Those additional\ntournaments are marked with a `permissionGrantedVisibility: true` flag.\n\n\nRequired token scope: `rest:read`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"header","name":"authorization","required":false,"schema":{"type":"string"},"description":"Optional bearer token. When provided and valid, draft and cancelled tournaments\nwhere the user holds a permission are appended to the response with\n`permissionGrantedVisibility: true`.\n"},{"in":"query","name":"platform","schema":{"type":"string"},"description":"A tournament platform's context ID. Returns the active tournaments that platform approved instead of BeatKhana's\nlisting, and ignores the authorization header. `beatkhana` returns BeatKhana's own approvals.\n"},{"in":"query","name":"additionalRegistrationFields","schema":{"type":"string","enum":["true","false"]},"description":"Include additional registration fields for each tournament."}],"responses":{"200":{"description":"List of tournaments retrieved successfully","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"permissionGrantedVisibility":{"type":"boolean","description":"Only present on tournaments that are draft or cancelled and were\nincluded solely because the authenticated user holds a permission\non that tournament. Never present on publicly visible tournaments.\n","example":true}}}}}}},"401":{"description":"Authorization token was provided but is invalid or expired","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! Please request a new token!"}}},"402":{"description":"No tournaments found","content":{"text/plain":{"schema":{"type":"string"},"example":"402: No Tournaments Found"}}}},"x-required-scopes":["rest:read"]}},"/tournaments/{guid}":{"delete":{"summary":"Delete an existing tournament","description":"PERMANENTLY delete a tournament. There is no way to undo this action other than a database rollback. Only the tournament organiser or users with global admin permissions can delete tournaments.\n\nRequired token scope: `rest:write`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string"},"description":"The GUID of the tournament to delete"}],"responses":{"200":{"description":"Tournament deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"Successfully deleted tournament: \"abc123-def456\""}}}}}},"400":{"description":"Bad request - missing authorization header","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! You are not the organiser of this tournament!"}}},"402":{"description":"Tournament GUID not specified","content":{"text/plain":{"schema":{"type":"string"},"example":"402: Tournament guid not specified"}}}},"x-required-scopes":["rest:write"]}},"/requestTournamentVisibilityApproval/{tournamentGuid}":{"post":{"deprecated":true,"summary":"Request visibility approval for a tournament (legacy)","description":"Legacy shortcut for `PUT /tournaments/{tournamentIdentifier}/visibility` that keeps the tournament's current scope. Rejected requests are submitted again; it fails when nothing new was requested. Only tournament organisers, admins, or users with global permissions can request visibility.\n\nRequired token scope: `rest:write`.","tags":["Tournament Visibility"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"},"description":"The GUID of the tournament requesting visibility approval"}],"responses":{"200":{"description":"Visibility request sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Visibility request has been sent!"}}}}}},"400":{"description":"Bad request - missing authorization, tournament already requested, or denied","content":{"text/plain":{"schema":{"type":"string"},"example":"This tournament has either already requested visibility or has beed rejected."}}},"401":{"description":"Unauthorized - invalid token or user not permitted","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! This user is not permitted to carry out this action!"}}},"402":{"description":"Tournament GUID not specified","content":{"text/plain":{"schema":{"type":"string"},"example":"402: Tournament guid not specified"}}},"404":{"description":"User or tournament not found in database","content":{"text/plain":{"schema":{"type":"string"},"example":"Your user was not found in the database"}}}},"x-required-scopes":["rest:write"]}},"/admin/tournaments":{"get":{"summary":"List every tournament for platform administration","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Tournaments with organiser","stream-key groups":null,"and requests":null},"401":{"description":"Authentication required"},"403":{"description":"Global tournament administration permission required"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/admin/tournaments/{identifier}":{"patch":{"summary":"Update platform-managed tournament details","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["draft","ongoing","completed","cancelled"]},"visibilityStatus":{"type":"string","enum":["invisible","requested","approved","denied"]},"type":{"type":"string","enum":["affiliated","independent","owned","verified","highlighted"]},"startDate":{"type":"string","format":"date-time","nullable":true},"endDate":{"type":"string","format":"date-time","nullable":true},"rateLimits":{"type":"object","properties":{"picksBansFlowLimit":{"type":"integer","minimum":0,"maximum":10000},"picksBansOverlayConfigLimit":{"type":"integer","minimum":0,"maximum":10000},"matchSceneLimits":{"type":"integer","minimum":0,"maximum":10000}}},"notifyOrganiser":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated tournament"},"400":{"description":"Invalid or empty update"},"403":{"description":"Global tournament administration permission required"},"404":{"description":"Tournament not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/admin/tournaments/{identifier}/transfer":{"post":{"summary":"Transfer tournament ownership to another BeatKhana user","tags":["Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["newOwnerDiscordId"],"properties":{"newOwnerDiscordId":{"type":"string","description":"Discord snowflake of the new owner"},"emailOldOrganiser":{"type":"boolean","default":false},"disregardNewOrganiserEmailStatus":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Ownership transferred and notification delivery reported"},"400":{"description":"Invalid Discord ID"},"403":{"description":"Administrator access or verified email requirement failed"},"404":{"description":"Tournament or new owner not found"},"409":{"description":"The selected user already owns the tournament"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/userTournamentsWithPermission/{permission}":{"get":{"summary":"Get tournaments where user has specific permission","description":"Retrieves all tournaments where the authenticated user has the specified permission or a higher role in the hierarchy\n\nRequired token scope: `rest:read`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"permission","required":true,"schema":{"type":"string","enum":["organiser","admin","staff","host","referee","coordinator","hcoordinator","hpooler","pooler","caster","player","hcaster","viewer"]},"description":"The permission type to check (without 'role:' prefix)"}],"responses":{"200":{"description":"List of tournaments retrieved successfully (may be empty array)","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"400":{"description":"Bad request - missing authorization header or invalid permission type","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized - invalid or expired token","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! Please request a new token!"}}},"404":{"description":"User not found in database","content":{"text/plain":{"schema":{"type":"string"},"example":"Your user was not found in the database"}}}},"x-required-scopes":["rest:read"]}},"/tournament/{identifier}":{"get":{"summary":"Get detailed tournament information","description":"Retrieves detailed tournament information with optional relations based on query parameters.\nAuthentication is optional for this endpoint. Some query options return data publicly,\nwhile others require authentication and specific permissions. When auth is required but\nnot provided or insufficient, the protected data is simply omitted from the response.\nUsers with global roles (role:owner, role:dev, global:tournaments:manage) bypass all\ntournament-level permission checks.\n\n\nRequired token scope: `rest:read`.","tags":["Tournaments"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"},{"in":"query","name":"players","schema":{"type":"string","enum":["true","false"]},"description":"Include tournament players. **No auth required** if the tournament's `showPlayers`\nsetting is enabled. If `showPlayers` is disabled, **auth is required** with one of\nthe following tournament permissions: role:organiser, role:staff, role:admin,\nrole:referee, role:host, role:hcoordinator, role:coordinator, role:hpooler,\nrole:caster, tournament:players:add, tournament:players:remove,\ntournament:players:edit, tournament:players:view.\n"},{"in":"query","name":"teams","schema":{"type":"string","enum":["true","false"]},"description":"Include tournament teams. Access level varies:\n- **No auth**: Only teams visible based on the tournament's `showRegistrations`\n  (registered teams) and `showPlayers` (qualified teams) settings. Invites are not included.\n- **Authenticated without sufficient permissions**: Public teams (as above) plus the\n  user's own team (as captain or member) with invites for their own team only.\n- **Authenticated with permissions**: All teams including invites. Required tournament\n  permissions: role:organiser, role:staff, role:admin, role:hcoordinator, role:hpooler,\n  tournament:players:add, tournament:players:remove, tournament:players:edit,\n  tournament:players:view, tournament:teams:create, tournament:teams:edit,\n  tournament:teams:delete, tournament:teams:view, tournament:teams:addPlayers,\n  tournament:teams:removePlayers.\n"},{"in":"query","name":"registrations","schema":{"type":"string","enum":["true","false"]},"description":"Include registrations. **No auth required** if the tournament's `showRegistrations`\nsetting is enabled. If `showRegistrations` is disabled, **auth is required** with one\nof the following tournament permissions: role:organiser, role:staff, role:admin,\nrole:referee, role:host, role:hcoordinator, role:coordinator, role:hpooler,\nrole:caster, tournament:players:add, tournament:players:remove,\ntournament:players:edit, tournament:players:view.\n"},{"in":"query","name":"organiser","schema":{"type":"string","enum":["true","false"]},"description":"Include tournament organiser. **No auth required.**"},{"in":"query","name":"staff","schema":{"type":"string","enum":["true","false"]},"description":"Include staff members. **No auth required.**"},{"in":"query","name":"admins","schema":{"type":"string","enum":["true","false"]},"description":"Include admins. **No auth required.**"},{"in":"query","name":"coordinators","schema":{"type":"string","enum":["true","false"]},"description":"Include coordinators. **No auth required.**"},{"in":"query","name":"poolers","schema":{"type":"string","enum":["true","false"]},"description":"Include poolers. **No auth required.**"},{"in":"query","name":"casters","schema":{"type":"string","enum":["true","false"]},"description":"Include casters. **No auth required.**"},{"in":"query","name":"hcoordinator","schema":{"type":"string","enum":["true","false"]},"description":"Include head coordinator. **No auth required.**"},{"in":"query","name":"hpooler","schema":{"type":"string","enum":["true","false"]},"description":"Include head pooler. **No auth required.**"},{"in":"query","name":"hcaster","schema":{"type":"string","enum":["true","false"]},"description":"Include head caster. **No auth required.**"},{"in":"query","name":"hosts","schema":{"type":"string","enum":["true","false"]},"description":"Include hosts. **No auth required.**"},{"in":"query","name":"qualifierSettings","schema":{"type":"string","enum":["true","false"]},"description":"Include qualifier settings. **No auth required.**"},{"in":"query","name":"qualifierStandings","schema":{"type":"string","enum":["true","false"]},"description":"Include the persisted backend-calculated qualifier standings under `qualifierStandings`. The response is built from normalized database rows and current registered/qualified teams; it does not contact TournamentAssistant. **No auth required.**\n"},{"in":"query","name":"donations","schema":{"type":"string","enum":["true","false"]},"description":"Include public prize pool settings and public-safe donation rows. **No auth required.**"},{"in":"query","name":"matches","schema":{"type":"string","enum":["true","false"]},"description":"Include divisions, brackets, and matches with full team details (teams,\nassigned coordinators, results with scores, map pool, overlays, picks/bans).\n**No auth required.**\n"},{"in":"query","name":"picksBansFlows","schema":{"type":"string","enum":["true","false"]},"description":"Include picks and bans flows. **No auth required.**"},{"in":"query","name":"mapFlairs","schema":{"type":"string","enum":["true","false"]},"description":"Include map flairs. **No auth required.**"},{"in":"query","name":"publishedPools","schema":{"type":"string","enum":["true","false"]},"description":"Include published map pools (type \"published\" only) with maps and flairs. **No auth required.**"},{"in":"query","name":"draftedPools","schema":{"type":"string","enum":["true","false"]},"description":"Include all map pools (both drafted and published) with maps, flairs, and comments.\n**Auth is required.** With sufficient permissions, all pools are returned including\ndrafted pools and map comments. Without sufficient permissions, falls back to published\npools only (no comments). Required tournament permissions: role:organiser, role:staff,\nrole:admin, role:hcoordinator, role:hpooler, role:pooler, tournament:pools:create,\ntournament:pools:editDrafted, tournament:pools:editPublished, tournament:pools:delete,\ntournament:pools:addMaps, tournament:pools:removeMaps.\n"},{"in":"query","name":"overlays","schema":{"type":"string","enum":["true","false"]},"description":"Include overlay settings with full match and style details. **Auth is required.**\nData is only returned if the user has one of the following tournament permissions:\nrole:organiser, role:staff, role:admin, role:referee, role:host, role:hcoordinator,\nrole:coordinator.\n"},{"in":"query","name":"timelineEvents","schema":{"type":"string","enum":["true","false"]},"description":"Include the tournament timeline events. **No auth required.**"},{"in":"query","name":"additionalRegistrationFields","schema":{"type":"string","enum":["true","false"]},"description":"Include additional registration fields. **No auth required.**"},{"in":"query","name":"additionalRegistrationFieldResponses","schema":{"type":"string","enum":["true","false"]},"description":"Include additional registration field responses.\n- **Authenticated with permissions**: role:organiser, role:staff, role:admin, role:hcoordinator (or global bypass) can view all responses.\n- **Authenticated without those permissions**: only the caller's own submitted responses are returned.\n- **No auth**: responses are omitted.\n"}],"responses":{"200":{"description":"Tournament data retrieved successfully. Protected fields are omitted if auth is missing or insufficient.","content":{"application/json":{"schema":{"type":"object","properties":{"qualifierStandings":{"allOf":[{"$ref":"#/components/schemas/QualifierStandings"}],"nullable":true}}}}}},"400":{"description":"Bad request - missing identifier, or missing authorization header when required by a protected query option (players, registrations, draftedPools, overlays)","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"404":{"description":"Tournament not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Tournament with this GUID or ID not found."}}}},"x-required-scopes":["rest:read"]}},"/getDiscordUser/{userid}":{"get":{"summary":"Get public profile data for a Discord user","tags":["Discord"],"parameters":[{"in":"path","name":"userid","required":true,"description":"The Discord user ID to fetch","schema":{"type":"string"}}],"responses":{"200":{"description":"Discord user profile returned successfully","content":{"application/json":{"schema":{"type":"object","description":"Discord user object","additionalProperties":true}}}},"400":{"description":"Missing userid","content":{"text/plain":{"schema":{"type":"string"},"example":"Please specify a discord user"}}},"500":{"description":"Failed to contact Discord API"}}}},"/getEventNotificationsToDisplay":{"get":{"summary":"Get all event notifications","tags":["Events"],"responses":{"200":{"description":"List of event notifications","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"guid":{"type":"string"},"eventId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"outline":{"type":"array","items":{"type":"string"}},"button1Text":{"type":"string","nullable":true},"button1Href":{"type":"string","nullable":true},"button1BgColor":{"type":"string","nullable":true},"button1GlowColor":{"type":"string","nullable":true},"button2Text":{"type":"string","nullable":true},"button2Href":{"type":"string","nullable":true},"button2BgColor":{"type":"string","nullable":true},"button2GlowColor":{"type":"string","nullable":true},"button3Text":{"type":"string","nullable":true},"button3Href":{"type":"string","nullable":true},"button3BgColor":{"type":"string","nullable":true},"button3GlowColor":{"type":"string","nullable":true}}}}}}}},"parameters":[]}},"/modifyEvent/{eventguid}":{"put":{"summary":"Modify an existing event notification","tags":["Events"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"name":"eventguid","in":"path","description":"GUID of the event to modify","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Updated event notification data","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"outline":{"type":"array","items":{"type":"string"}},"button1Text":{"type":"string","nullable":true},"button1Href":{"type":"string","nullable":true},"button1BgColor":{"type":"string","nullable":true},"button1GlowColor":{"type":"string","nullable":true},"button2Text":{"type":"string","nullable":true},"button2Href":{"type":"string","nullable":true},"button2BgColor":{"type":"string","nullable":true},"button2GlowColor":{"type":"string","nullable":true},"button3Text":{"type":"string","nullable":true},"button3Href":{"type":"string","nullable":true},"button3BgColor":{"type":"string","nullable":true},"button3GlowColor":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Event updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"success!"}}}}}},"400":{"description":"Missing authorization token or event ID or invalid input","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized due to invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized! Please request a new token!"}}}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/createEvent":{"post":{"summary":"Create a new event notification","tags":["Events"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"description":"New event notification data","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"outline":{"type":"array","items":{"type":"string"}},"button1Text":{"type":"string","nullable":true},"button1Href":{"type":"string","nullable":true},"button1BgColor":{"type":"string","nullable":true},"button1GlowColor":{"type":"string","nullable":true},"button2Text":{"type":"string","nullable":true},"button2Href":{"type":"string","nullable":true},"button2BgColor":{"type":"string","nullable":true},"button2GlowColor":{"type":"string","nullable":true},"button3Text":{"type":"string","nullable":true},"button3Href":{"type":"string","nullable":true},"button3BgColor":{"type":"string","nullable":true},"button3GlowColor":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Event created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"success!"}}}}}},"400":{"description":"Missing authorization token or invalid input","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an authorization header token!"}}},"401":{"description":"Unauthorized due to invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized!"}}}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/deleteEvent/{eventid}":{"delete":{"summary":"Delete an event notification","tags":["Events"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"eventid","required":true,"description":"The eventId of the notification to delete","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully deleted event","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"success!"}}}}}},"400":{"description":"Missing event ID or authorization token","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide an event id!"}}},"401":{"description":"Unauthorized or no permission","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized!"}}}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/embeds/home.png":{"get":{"summary":"Render the BeatKhana social preview image","tags":["Embeds"],"responses":{"200":{"description":"A 1200 by 630 PNG social preview","content":{"image/png":{}}}},"parameters":[]}},"/embeds/pages/{section}.png":{"get":{"summary":"Render a generic BeatKhana page social preview","tags":["Embeds"],"parameters":[{"in":"path","name":"section","required":true,"schema":{"type":"string"}},{"in":"query","name":"title","schema":{"type":"string"}},{"in":"query","name":"description","schema":{"type":"string"}},{"in":"query","name":"section","schema":{"type":"string"}},{"in":"query","name":"background","description":"Background image URL","schema":{"type":"string","format":"uri"}},{"in":"query","name":"image","description":"Foreground image URL","schema":{"type":"string","format":"uri"}}],"responses":{"200":{"description":"A 1200 by 630 PNG social preview","content":{"image/png":{}}}}}},"/embeds/tournaments/{identifier}.png":{"get":{"summary":"Render a tournament, match, or bracket social preview","tags":["Embeds"],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"query","name":"tournament","schema":{"type":"string"}},{"in":"query","name":"title","schema":{"type":"string"}},{"in":"query","name":"description","schema":{"type":"string"}},{"in":"query","name":"background","description":"Tournament banner URL used as the background","schema":{"type":"string","format":"uri"}},{"in":"query","name":"image","description":"Tournament image URL used as foreground art","schema":{"type":"string","format":"uri"}},{"in":"query","name":"match","description":"Match number; enables the versus-card layout","schema":{"type":"string"}},{"in":"query","name":"matchType","schema":{"type":"string"}},{"in":"query","name":"leftName","schema":{"type":"string"}},{"in":"query","name":"rightName","schema":{"type":"string"}},{"in":"query","name":"leftImage","schema":{"type":"string","format":"uri"}},{"in":"query","name":"rightImage","schema":{"type":"string","format":"uri"}},{"in":"query","name":"leftScore","description":"Points of the left competitor; with rightScore, shows the score instead of VS","schema":{"type":"integer"}},{"in":"query","name":"rightScore","schema":{"type":"integer"}},{"in":"query","name":"bracket","description":"Bracket name; enables the bracket-card layout when match is absent","schema":{"type":"string"}},{"in":"query","name":"bracketMatches","description":"JSON array containing up to four bracket match summaries","schema":{"type":"string"}}],"responses":{"200":{"description":"A 1200 by 630 PNG social preview","content":{"image/png":{}}}}}},"/sendEmailVerification":{"post":{"summary":"Request an email verification code","description":"Sends a verification email to the user. A BeatKhana JWT (`Authorization` header) is required.\n\nRequired token scope: `rest:user:write`.","tags":["Email Verification"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"}}}}}},"responses":{"200":{"description":"Verification email sent successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"messageId":{"type":"string","example":"<smtp-message-id>"}}}}}},"400":{"description":"Missing email or invalid user."},"401":{"description":"Unauthorized or pending request already exists."},"500":{"description":"Failed to send email."}},"parameters":[],"x-required-scopes":["rest:user:write"]}},"/resendEmailVerification":{"post":{"summary":"Resend an existing email verification code","description":"Resends the previously generated verification code. A BeatKhana JWT is required.\n\nRequired token scope: `rest:user:write`.","tags":["Email Verification"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","example":"user@example.com"}}}}}},"responses":{"200":{"description":"Verification email resent successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"messageId":{"type":"string"}}}}}},"400":{"description":"Missing or invalid email."},"401":{"description":"No pending request or unauthorized."},"500":{"description":"SMTP failure or internal error."}},"parameters":[],"x-required-scopes":["rest:user:write"]}},"/cancelVerificationRequest":{"delete":{"summary":"Cancel a pending email verification request","description":"Removes the currently pending verification request for the authenticated user.\n\nRequired token scope: `rest:user:write`.","tags":["Email Verification"],"security":[{"BeatKhanaAuth":["rest:user:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Verification request successfully cancelled.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully removed the email verification request!"}}}}}},"400":{"description":"Missing email or user not found."},"401":{"description":"No pending request available or unauthorized."}},"parameters":[],"x-required-scopes":["rest:user:write"]}},"/verifyEmail/{code}":{"post":{"summary":"Verify an email using a verification code","description":"Confirms a user's email address using the verification code sent via email.","tags":["Email Verification"],"security":[{"BeatKhanaAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"},"description":"The email verification code sent to the user."}],"responses":{"200":{"description":"Email successfully verified.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully verified email!"}}}}}},"400":{"description":"Missing verification code or user not found."},"401":{"description":"Invalid or incorrect verification code."}}}},"/tournaments/{tournamentIdentifier}/general":{"put":{"summary":"Update general settings for a tournament","description":"Updates high-level tournament data such as name, images, signup settings, visibility, and registration rules. Requires valid BeatKhana JWT and appropriate tournament permissions.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Settings"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"The unique identifier of the tournament.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"imageUrl":{"type":"string"},"bannerUrl":{"type":"string"},"description":{"type":"string"},"rules":{"type":"string"},"discordLink":{"type":"string"},"twitchName":{"type":"string"},"status":{"type":"string"},"twitchRequired":{"type":"boolean"},"blRequired":{"type":"boolean","deprecated":true},"rankingAccountRequirement":{"type":"string","enum":["none","beatleader","scoresaber","either","both"]},"showPlayers":{"type":"boolean"},"showRegistrations":{"type":"boolean"},"signUpStart":{"type":"number","description":"Timestamp (ms)"},"signUpEnd":{"type":"number","description":"Timestamp (ms)"},"acceptedPlatform":{"type":"string"},"countryFilter":{"type":"array","items":{"type":"string"}},"visibilityStatus":{"type":"string"},"permittedTopLevelEmails":{"type":"array","items":{"type":"string"}},"allowSoloRegistration":{"type":"boolean"},"allowTeamRegistrations":{"type":"boolean"},"allowCaptainPlaying":{"type":"boolean"},"maxTeamSize":{"type":"number"},"minTeamSize":{"type":"number"},"maxTeams":{"type":"number"},"allowTeamModification":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Successfully updated general tournament settings."},"400":{"description":"Invalid tournament or invalid body data."},"401":{"description":"Unauthorized or insufficient permissions."},"500":{"description":"Server or database update error."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/qualifiers":{"put":{"summary":"Update qualifier settings for a tournament","description":"Saves the selected TournamentAssistant tournament and qualifier GUIDs, cutoff values, and the complete dynamic scoring algorithm. Saving first recalculates from the last-known-good cache and then attempts a fresh TA `/all` synchronization. If TA is unavailable, the saved settings and previous cached standings remain available and the response includes a warning. Score-selection rules can discard N entries or keep only N entries. `playerScores` applies across each player's maps, `mapScores` applies across all players on each map, and `teamPlayers` applies independently to each team on each map. Requires valid BeatKhana JWT and correct tournament permissions.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Settings"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament identifier."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["taTournamentGuid","taQualifierGuid"],"properties":{"taTournamentGuid":{"type":"string","minLength":1,"description":"TournamentAssistant tournament GUID selected through moons-ta-client."},"taQualifierGuid":{"type":"string","minLength":1,"description":"TournamentAssistant qualifier event GUID belonging to the selected tournament."},"excludedMapGuids":{"type":"array","maxItems":500,"uniqueItems":true,"description":"TournamentAssistant qualifier map GUIDs omitted from all standings and calculations.","items":{"type":"string","minLength":1,"maxLength":255}},"cutoff":{"type":"number"},"scoringConfig":{"type":"object","properties":{"formula":{"type":"string","maxLength":400},"forceBestScoreToOne":{"type":"boolean"},"singleScoreValue":{"type":"number"},"includePlaceholderScores":{"type":"boolean","description":"Give each registered player without a map submission an all-zero placeholder score."},"teamMapAggregate":{"type":"string","enum":["sum","avg"]},"teamOverallAggregate":{"type":"string","enum":["sum","avg"]},"scoreConditions":{"type":"array","maxItems":20,"items":{"type":"object","required":["variable","operator"],"properties":{"id":{"type":"string"},"label":{"type":"string","maxLength":120},"variable":{"type":"string","enum":["playerScore","bestScore","minScore","maxScore","scoreRatio","position","totalPlayers","positionScore","accuracy","misses","badCuts","mistakes","maxCombo"]},"operator":{"type":"string","enum":["gt","gte","lt","lte","eq","neq"]},"value":{"type":"number"},"action":{"type":"string","enum":["exclude"],"default":"exclude"}}}},"discardRules":{"type":"array","maxItems":10,"items":{"type":"object","required":["scope","operation","metric","order","count","onlyIf"],"properties":{"id":{"type":"string"},"label":{"type":"string","maxLength":120},"scope":{"type":"string","enum":["playerScores","mapScores","teamPlayers"]},"operation":{"type":"string","enum":["discard","keep"]},"metric":{"type":"string","enum":["weightedScore","accuracy","playerScore","teamOverall","misses","badCuts","mistakes","maxCombo","position"]},"order":{"type":"string","enum":["lowest","highest"]},"count":{"type":"integer","minimum":0},"onlyIf":{"type":"string","enum":["always","playedAllMaps"]}}}}}}}}}}},"responses":{"200":{"description":"Settings saved; synchronization status is included even when TA is unavailable.","content":{"application/json":{"schema":{"type":"object","required":["success","recalculation","synchronization"],"properties":{"success":{"type":"string"},"recalculation":{"$ref":"#/components/schemas/QualifierSyncResult"},"synchronization":{"$ref":"#/components/schemas/QualifierSyncResult"},"warning":{"type":"string","description":"Present when settings were saved but the fresh TA synchronization failed."}}}}}},"400":{"description":"Invalid tournament identifier, tournament state, TA GUID selection, or scoring configuration."},"401":{"description":"Authentication failed or the caller lacks qualifier settings permission."},"500":{"description":"Failed to update qualifier settings."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/visibility/approve":{"post":{"summary":"Approve tournament visibility request","description":"Marks the tournament as **approved**, sends email notifications, and triggers a Discord webhook. Only global moderators (dev, owner, global tournament managers) may execute.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Visibility"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament identifier."}],"responses":{"200":{"description":"Successfully approved tournament visibility."},"400":{"description":"Missing identifier."},"401":{"description":"Not authorized."},"404":{"description":"Tournament or user not found."},"500":{"description":"Error sending email or updating tournament."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/visibility/reject":{"post":{"summary":"Reject tournament visibility request","description":"Rejects the tournament on the BeatKhana platform (visibilityStatus becomes **denied**). Organisers may request again. An optional `reason` is shown to them. To block a tournament permanently (which also cancels it), use the BeatKhana platform queue: `POST /tournament-platforms/beatkhana/requests/{requestGuid}/review`. Sends rejection emails and a Discord webhook notification. Only global moderators may perform this action.\n\n\nRequired token scope: `rest:write`.","tags":["Tournament Visibility"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament identifier."}],"responses":{"200":{"description":"Successfully rejected tournament visibility."},"400":{"description":"Missing identifier."},"401":{"description":"User lacks sufficient privileges."},"404":{"description":"Tournament or organiser not found."},"500":{"description":"Internal error while rejecting visibility."}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/donations/settings":{"get":{"summary":"Get tournament donation setup settings","tags":["Donations"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Prize pool","platform settings":null,"webhook URLs":null,"and editable donation rows":null},"400":{"description":"Missing authorization or tournament identifier"},"401":{"description":"Unauthorized or insufficient tournament permissions"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"put":{"summary":"Update tournament prize pool and donation platform settings","tags":["Donations"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Donation settings saved"},"400":{"description":"Missing authorization or invalid settings"},"401":{"description":"Unauthorized or insufficient tournament permissions"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/donations/manual":{"post":{"summary":"Add a manual donation","tags":["Donations"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Donation created"},"400":{"description":"Missing authorization or invalid payload"},"401":{"description":"Unauthorized or insufficient tournament permissions"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/donations/{donationGuid}":{"put":{"summary":"Edit a tournament donation","tags":["Donations"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"donationGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Donation updated"},"400":{"description":"Missing authorization"},"401":{"description":"Unauthorized or insufficient tournament permissions"},"404":{"description":"Donation not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Remove a tournament donation from public totals","tags":["Donations"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"donationGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Donation removed"},"400":{"description":"Missing authorization"},"401":{"description":"Unauthorized or insufficient tournament permissions"},"404":{"description":"Donation not found"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentGuid}/donations/kofi":{"post":{"summary":"Receive a Ko-fi payment webhook","tags":["Donations","Webhooks"],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook accepted"},"400":{"description":"Invalid Ko-fi payload"},"403":{"description":"Ko-fi integration disabled or token invalid"}}}},"/tournaments/{tournamentGuid}/donations/paypal":{"post":{"summary":"Receive a PayPal payment webhook","tags":["Donations","Webhooks"],"parameters":[{"in":"path","name":"tournamentGuid","required":true,"schema":{"type":"string"}},{"in":"query","name":"secret","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook accepted"},"403":{"description":"PayPal integration disabled or secret invalid"}}}},"/tournaments/{tournamentIdentifier}/divisions/{optionalDivisionGuid}":{"get":{"summary":"Get division(s)","description":"Get a specific division or all divisions in a tournament\n\nRequired token scope: `rest:read`.","tags":["Divisions"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"optionalDivisionGuid"}],"responses":{"200":{"description":"Division(s) retrieved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/divisions":{"post":{"summary":"Create a new division","description":"Create a new division in a tournament\n\nRequired token scope: `rest:write`.","tags":["Divisions"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","id"],"properties":{"name":{"type":"string"},"id":{"type":"string"}}}}}},"responses":{"200":{"description":"Division created successfully"},"400":{"description":"Bad request or validation error"},"401":{"description":"Unauthorized"}},"x-required-scopes":["rest:write"]},"get":{"summary":"Get division(s)","description":"Get a specific division or all divisions in a tournament\n\nRequired token scope: `rest:read`.\n\nThe `optionalDivisionGuid` path segment is optional; this form omits it.","tags":["Divisions"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"}],"responses":{"200":{"description":"Division(s) retrieved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/divisions/reorder":{"put":{"summary":"Reorder the divisions of a tournament","description":"Saves the visual order of every division as a single atomic operation. `orderedGuids` must\nlist each division exactly once; when the divisions changed since the client loaded them the\nrequest fails with 409 and nothing moves. Requires tournament organiser, admin or staff\npermissions (or global tournament management).\n\n\nRequired token scope: `rest:write`.","tags":["Divisions"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["orderedGuids"],"properties":{"orderedGuids":{"type":"array","items":{"type":"string","format":"uuid"},"uniqueItems":true,"description":"Division GUIDs in their new order"}}}}}},"responses":{"200":{"description":"Divisions reordered"},"400":{"description":"Duplicate or missing GUIDs"},"401":{"description":"Not signed in or not permitted"},"404":{"description":"Tournament not found"},"409":{"description":"The division list changed; reload and try again"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/divisions/{divisionGuid}":{"put":{"summary":"Update a division","description":"Update division details. Cannot change tournament GUID.\n\nRequired token scope: `rest:write`.","tags":["Divisions"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"divisionGuid"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"id":{"type":"string"}}}}}},"responses":{"200":{"description":"Division updated successfully"},"400":{"description":"Bad request or validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a division and all its brackets and matches","description":"Permanently deletes a division and all child brackets and matches\n\nRequired token scope: `rest:write`.","tags":["Divisions"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"divisionGuid"}],"responses":{"200":{"description":"Division deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/discordIntegration":{"get":{"summary":"Get tournament Discord integration settings","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Current Discord integration state, role mappings, available templates, and linked guild resources."},"400":{"description":"Invalid tournament or Discord bot state"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"patch":{"summary":"Update tournament Discord integration settings","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"serverId":{"type":"string","nullable":true},"syncFrom":{"type":"boolean"},"syncTo":{"type":"boolean"},"teamsThreadChannel":{"type":"string","nullable":true},"enableCreateThreadsForTeams":{"type":"boolean"},"announcementChannelId":{"type":"string","nullable":true},"announcementTemplate":{"type":"string","description":"Prefix and suffix around {{content}}"},"donationNotificationsEnabled":{"type":"boolean"},"donationNotificationsChannelId":{"type":"string","nullable":true},"donationNotificationsMessage":{"type":"string"},"donationNotificationsEmbed":{"type":"object"},"adminRoleId":{"type":"string","nullable":true},"staffRoleId":{"type":"string","nullable":true},"hCoordinatorRoleId":{"type":"string","nullable":true},"coordinatorRoleId":{"type":"string","nullable":true},"refereeRoleId":{"type":"string","nullable":true},"hPoolerRoleId":{"type":"string","nullable":true},"poolerRoleId":{"type":"string","nullable":true},"hCasterRoleId":{"type":"string","nullable":true},"casterRoleId":{"type":"string","nullable":true},"streamHostRoleId":{"type":"string","nullable":true},"registeredRoleId":{"type":"string","nullable":true},"qualifiedRoleId":{"type":"string","nullable":true},"currentMatchRoleId":{"type":"string","nullable":true,"description":"Given to the teams of a match by its referees and stream hosts"}}}}}},"responses":{"200":{"description":"Discord integration settings saved"},"400":{"description":"Invalid settings or insufficient Discord role hierarchy"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/discordIntegration/mutualGuilds":{"get":{"summary":"List Discord guilds that the user and bot can administer","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Mutual guilds and bot invite URL"},"400":{"description":"Could not load mutual guilds"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/discordIntegration/resources/{guildId}":{"get":{"summary":"Get Discord roles and channel resources for a guild","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"guildId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Guild roles below the bot and eligible text channels"},"400":{"description":"Could not load guild resources"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/discordIntegration/syncTo":{"post":{"summary":"Additively synchronise Discord roles and BeatKhana tournament permissions","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Sync result with updated and skipped counts"},"400":{"description":"Could not synchronise Discord roles"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/discordIntegration/setup":{"post":{"summary":"Create Discord roles and channel template items for a tournament","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"roleThemeId":{"type":"string","example":"beatkhana"},"roleKeys":{"type":"array","items":{"type":"string"}},"channelTemplateId":{"type":"string","example":"completeStaff"},"categoryIds":{"type":"array","items":{"type":"string"}},"channelIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Created/reused Discord roles","categories":null,"and channels":null},"400":{"description":"Invalid setup request or missing bot permissions"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/discordMembership":{"get":{"summary":"Check whether the authenticated user meets the Discord membership requirement","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Requirement status and whether the user meets it"},"400":{"description":"Could not check Discord membership"},"401":{"description":"Authentication required"}},"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/admin/discord":{"get":{"summary":"Get Discord bot administration dashboard data","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Current admin user","bot settings":null,"bot guild count":null,"member count":null,"and server list":null},"400":{"description":"Could not load Discord bot administration"},"401":{"description":"Owner or developer role required"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"patch":{"summary":"Update global Discord bot settings","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"createAccountsForKnown":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Discord bot settings saved"},"400":{"description":"Could not save Discord bot settings"},"401":{"description":"Owner or developer role required"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/admin/discord/findAllUsers":{"post":{"summary":"Import or update all known Discord users from every bot guild","tags":["Discord Integration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Import results for each Discord guild"},"400":{"description":"Could not import Discord users"},"401":{"description":"Owner or developer role required"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/discord/botInvite":{"get":{"summary":"Get the Discord bot invite URL","tags":["Discord Integration"],"responses":{"200":{"description":"Bot invite URL with Administrator permission request"},"400":{"description":"Could not create Discord bot invite"}},"parameters":[]}},"/communities":{"get":{"summary":"Discover community organisations","tags":["Communities"],"responses":{"200":{"description":"Public community list"}},"parameters":[]},"post":{"summary":"Create a community organisation","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Community created"},"400":{"description":"Invalid data"},"403":{"description":"Verified email required"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/communities/{identifier}":{"get":{"summary":"Get a community profile","tags":["Communities"],"responses":{"200":{"description":"Community profile"},"404":{"description":"Not found"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}]},"patch":{"summary":"Update a community profile","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Community updated"},"403":{"description":"Community admin required"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/communities/{identifier}/manage":{"get":{"summary":"Get community administration data","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Management data"},"403":{"description":"Community admin required"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/communities/{identifier}/invitations":{"post":{"summary":"Invite a community member or administrator","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userIdentifier","role"],"properties":{"userIdentifier":{"type":"string"},"role":{"type":"string","enum":["member","admin"]},"roleName":{"type":"string","nullable":true,"maxLength":64,"description":"Optional member label; letters","numbers and spaces.":null}}}}}},"responses":{"201":{"description":"Invitation sent"},"400":{"description":"Invalid role"},"403":{"description":"Insufficient access or verified email required"},"404":{"description":"User or community not found"},"409":{"description":"User already joined or invited"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/community-invitations":{"get":{"summary":"List the current user's pending community invitations","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Pending invitations"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/community-invitations/{guid}/respond":{"post":{"summary":"Accept or decline a community invitation","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Invitation answered"},"403":{"description":"Verified email required for admins"}},"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/communities/{identifier}/members/{userIdentifier}":{"patch":{"summary":"Change a community member's custom role label","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["roleName"],"properties":{"roleName":{"type":"string","nullable":true,"maxLength":64,"description":"Letters","numbers and spaces; null restores Member.":null}}}}}},"responses":{"200":{"description":"Member updated"},"400":{"description":"Invalid role name"},"403":{"description":"Community administrator required"},"404":{"description":"Member not found"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Remove a community member","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Member removed"},"403":{"description":"Creator authority required for admins"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/communities/{identifier}/announcements":{"post":{"summary":"Publish a community Markdown announcement","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Announcement published"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/communities/{identifier}/announcements/{announcementId}":{"patch":{"summary":"Update a community announcement","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Announcement updated"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"announcementId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Delete a community announcement","tags":["Communities"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Announcement deleted"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"announcementId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"get":{"summary":"Get a public community announcement","tags":["Communities"],"responses":{"200":{"description":"Announcement"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"announcementId","required":true,"schema":{"type":"string"}}]}},"/tournaments/{identifier}/co-organisers":{"post":{"summary":"Add an individual tournament co-organiser","tags":["Tournament Organisers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Co-organiser added"},"403":{"description":"Creator access required"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{identifier}/co-organisers/{userIdentifier}":{"delete":{"summary":"Remove an individual tournament co-organiser","tags":["Tournament Organisers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Co-organiser removed"},"403":{"description":"Creator access required"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{identifier}/organiser-communities":{"post":{"summary":"Add or request a community tournament organiser","tags":["Tournament Organisers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Community added or invited"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{identifier}/organiser-communities/{communityIdentifier}/respond":{"post":{"summary":"Accept or decline a community organiser request","tags":["Tournament Organisers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Request answered"},"403":{"description":"Community admin required"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"communityIdentifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{identifier}/organiser-communities/{communityIdentifier}":{"delete":{"summary":"Remove a community tournament organiser","tags":["Tournament Organisers"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Community organiser removed"},"403":{"description":"Creator access required"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"communityIdentifier","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/cdn/upload":{"post":{"summary":"Upload a file to the CDN","security":[{"BeatKhanaAuth":["rest:write"]}],"description":"Uploads a file using multipart/form-data (Busboy). Requires an authorization token. Supports specifying an `uploadType` which must match the allowed enum values.\n\n\nRequired token scope: `rest:write`.","tags":["CDN"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"},"uploadType":{"type":"string","description":"Category/type of the uploaded file."}}}}}},"responses":{"200":{"description":"File uploaded successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"guid":{"type":"string"},"filename":{"type":"string"},"url":{"type":"string"},"type":{"type":"string"}}}}}},"400":{"description":"Missing file or invalid upload type."},"401":{"description":"Unauthorized or invalid token."},"500":{"description":"File processing or server error."}},"parameters":[],"x-required-scopes":["rest:write"]}},"/cdn/view/{filename}":{"get":{"summary":"View or download a file from the CDN","description":"Returns a file (image, video, document, etc.). If the file is an image, optional resizing parameters are supported.\n","tags":["CDN"],"parameters":[{"in":"path","name":"filename","required":true,"description":"Name of the file to retrieve.","schema":{"type":"string"}},{"in":"query","name":"s","required":false,"description":"Scale size (fit longest side to this dimension).","schema":{"type":"integer"}},{"in":"query","name":"w","required":false,"description":"Resize width.","schema":{"type":"integer"}},{"in":"query","name":"h","required":false,"description":"Resize height.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Returns the file (binary payload).","content":{"*":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"File not found."},"500":{"description":"Server or resizing error."}}}},"/cdn/images":{"get":{"summary":"List all images belonging to the authenticated user","description":"Returns all uploaded images for the current user.\n\nRequired token scope: `rest:read`.","tags":["CDN"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"User's images returned successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"images":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Missing authorization header."},"401":{"description":"Invalid or expired token."},"500":{"description":"Database or server error."}},"parameters":[],"x-required-scopes":["rest:read"]}},"/cdn/delete/{filename}":{"delete":{"summary":"Delete a file from the CDN","description":"Deletes a file the user owns. Admin users may have expanded permissions depending on logic.\n\n\nRequired token scope: `rest:write`.","tags":["CDN"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"filename","required":true,"description":"Name of the file to delete.","schema":{"type":"string"}}],"responses":{"200":{"description":"File deleted successfully."},"400":{"description":"Missing authorization header."},"401":{"description":"Unauthorized."},"403":{"description":"User does not own the file."},"404":{"description":"File not found."},"500":{"description":"Internal server or database error."}},"x-required-scopes":["rest:write"]}},"/cdn/uploads/{identifier}":{"get":{"summary":"Retrieve uploaded files by user identifier","description":"- `@me` → gets the user's own uploads - `all` → requires `cdn:manage` permission - specific ID/guid → returns that user's uploads (only if permitted)\n\n\nRequired token scope: `rest:read`.","tags":["CDN"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"},"description":"User identifier: `@me`, `\"all\"`, user Discord ID, or user GUID.\n"}],"responses":{"200":{"description":"List of images returned.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}},"400":{"description":"Missing authorization or identifier."},"401":{"description":"Unauthorized or insufficient permissions."},"500":{"description":"Server error."}},"x-required-scopes":["rest:read"]}},"/calendar":{"get":{"summary":"Retrieve events from the Google Calendar feed","description":"Returns a list of calendar events between `timeMin` and `timeMax`. Times must be valid ISO date strings. Events come from two sources: the configured Google Calendar and CubeCouch guests. CubeCouch events start at 19:30 CET and last 2 hours (until 21:30 CET) on their `scheduledDate`.\n","tags":["Calendar"],"parameters":[{"in":"query","name":"timeMin","required":true,"description":"The minimum datetime (inclusive). Must be an ISO date string.","schema":{"type":"string","example":"2025-01-01T00:00:00Z"}},{"in":"query","name":"timeMax","required":true,"description":"The maximum datetime (inclusive). Must be an ISO date string.","schema":{"type":"string","example":"2025-02-01T00:00:00Z"}}],"responses":{"200":{"description":"A list of calendar events.","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"summary":{"type":"string"},"description":{"type":"string"},"type":{"type":"string","enum":["googleCalendar","cubeCouch","beatkhana","other"]},"externalLink":{"type":"string"},"scheduleUrl":{"type":"string"},"tournamentId":{"type":"string"},"tournamentName":{"type":"string"},"tournamentImageUrl":{"type":"string","nullable":true},"icon":{"type":"string"},"isLive":{"type":"boolean"}}}}}}}}},"400":{"description":"Missing or invalid timeMin/timeMax query parameters."},"500":{"description":"Google Calendar API error or unexpected server error."}}}},"/tournaments/{tournamentIdentifier}/brackets/{optionalBracketGuid}":{"get":{"summary":"Get bracket(s)","description":"Get a specific bracket or all brackets in a tournament\n\nRequired token scope: `rest:read`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"optionalBracketGuid"}],"responses":{"200":{"description":"Bracket(s) retrieved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/brackets":{"post":{"summary":"Create a new bracket","description":"Create a new bracket in a division\n\nRequired token scope: `rest:write`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["divisionGuid","name","id"],"properties":{"divisionGuid":{"type":"string"},"name":{"type":"string"},"id":{"type":"string"}}}}}},"responses":{"200":{"description":"Bracket created successfully"},"400":{"description":"Bad request or validation error"},"401":{"description":"Unauthorized"}},"x-required-scopes":["rest:write"]},"get":{"summary":"Get bracket(s)","description":"Get a specific bracket or all brackets in a tournament\n\nRequired token scope: `rest:read`.\n\nThe `optionalBracketGuid` path segment is optional; this form omits it.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"}],"responses":{"200":{"description":"Bracket(s) retrieved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentIdentifier}/brackets/reorder":{"put":{"summary":"Reorder the brackets of a division","description":"Saves the visual order of every bracket in one division as a single atomic operation.\n`orderedGuids` must list each bracket of the division exactly once; when the division's\nbrackets changed since the client loaded them the request fails with 409 and nothing moves.\nRequires tournament organiser, admin or staff permissions (or global tournament management).\n\n\nRequired token scope: `rest:write`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"Tournament GUID or id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["divisionGuid","orderedGuids"],"properties":{"divisionGuid":{"type":"string","format":"uuid"},"orderedGuids":{"type":"array","items":{"type":"string","format":"uuid"},"uniqueItems":true,"description":"Bracket GUIDs in their new order"}}}}}},"responses":{"200":{"description":"Brackets reordered"},"400":{"description":"Missing division or duplicate GUIDs"},"401":{"description":"Not signed in or not permitted"},"404":{"description":"Tournament or division not found"},"409":{"description":"The bracket list changed; reload and try again"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/brackets/{bracketGuid}":{"put":{"summary":"Update a bracket","description":"Update bracket details. Can change division GUID to move bracket. Cannot change created date.\n\nRequired token scope: `rest:write`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"bracketGuid"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"divisionGuid":{"type":"string"},"name":{"type":"string"},"id":{"type":"string"}}}}}},"responses":{"200":{"description":"Bracket updated successfully"},"400":{"description":"Bad request or validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete a bracket and all its matches","description":"Permanently deletes a bracket and all matches within it\n\nRequired token scope: `rest:write`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier"},{"in":"path","name":"bracketGuid"}],"responses":{"200":{"description":"Bracket deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Not found"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generateSingleElimination/{teamAmount}":{"post":{"summary":"Generate a single elimination bracket","tags":["Bracket Generation"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"assignTeams":{"type":"boolean","default":true,"description":"Assign seeded teams automatically. Set false to leave all team slots empty."},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"}}}}}},"responses":{"200":{"description":"Single elimination bracket generated"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to generate bracket"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generateDoubleElimination/{teamAmount}":{"post":{"summary":"Generate a double elimination bracket","tags":["Bracket Generation"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"assignTeams":{"type":"boolean","default":true,"description":"Assign seeded teams automatically. Set false to leave all team slots empty."},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"}}}}}},"responses":{"200":{"description":"Double elimination bracket generated"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to generate bracket"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generate-single-elimination/{teamAmount}":{"post":{"summary":"Generate single elimination bracket","description":"Automatically generates a complete single elimination bracket structure within the specified division.\n\n**What this endpoint does:**\n1. Validates that the division exists and belongs to the specified tournament.\n2. Creates a new bracket (named \"divisionGuid-upper-randomNumber\") inside the division.\n3. Generates all matches for a single elimination bracket based on the team count.\n4. Creates a `matchResults` row for each match with default scores of 0.\n\n**Bracket structure:**\n- For power-of-2 team counts, every round is fully populated.\n- For non-power-of-2 team counts, the first round only has enough matches to reduce the field to a power of 2 (remaining teams receive byes into round 2. If you want a different structure please consult challonge and create the remaining matches manually).\n- Match types are labeled: \"Round N\", \"Quarter Finals\", \"Semi Finals\", \"Finals\".\n- All generated matches have `status: \"scheduled\"` and `loserMatchGuid: null` (no loser bracket in single elimination).\n\n**Permissions required:** Tournament organiser, admin, staff, head coordinator, or a global tournament management role.\n\n\nRequired token scope: `rest:write`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"The tournament's GUID or human-readable ID"},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string","format":"uuid"},"description":"The GUID of the division in which to create the bracket"},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer","minimum":2},"description":"The number of teams participating. Must be at least 2. Determines the bracket size and number of rounds."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"assignTeams":{"type":"boolean","default":true,"description":"Assign seeded teams automatically. Set false to leave all team slots empty."},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"}}}}}},"responses":{"200":{"description":"Bracket generated successfully. Returns the newly created bracket and all its matches.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully generated the single elimination bracket!"},"bracket":{"type":"object","description":"The newly created bracket row","properties":{"guid":{"type":"string","format":"uuid"},"divisionGuid":{"type":"string","format":"uuid"},"id":{"type":"string","example":"bracket"},"name":{"type":"string","example":"Bracket"},"sequence":{"type":"integer","example":1},"createdAt":{"type":"string","format":"date-time"}}},"newMatches":{"type":"array","description":"All generated matches in insertion order (finals first, round 1 last)","items":{"type":"object","properties":{"guid":{"type":"string","format":"uuid"},"tournamentGuid":{"type":"string","format":"uuid"},"bracketGuid":{"type":"string","format":"uuid"},"status":{"type":"string","example":"scheduled"},"matchNumber":{"type":"integer","description":"Display-only match number"},"matchType":{"type":"string","description":"Round N, Quarter Finals, Semi Finals, or Finals"},"nextMatchGuid":{"type":"string","format":"uuid","nullable":true,"description":"GUID of the match the winner advances to. Null for the Finals match."},"loserMatchGuid":{"type":"string","format":"uuid","nullable":true,"description":"Always null in single elimination"},"results":{"type":"string","format":"uuid","description":"GUID of the associated match_results row"}}}}}}}}},"400":{"description":"Missing or invalid required parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Missing required parameters"}}},"401":{"description":"Unauthorized — invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized!"}}},"404":{"description":"Tournament, user, or division not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Division not found or does not belong to this tournament!"}}},"500":{"description":"Internal server error during bracket generation","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to generate bracket"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generate-double-elimination/{teamAmount}":{"post":{"summary":"Generate double elimination bracket","description":"Automatically generates a complete double elimination bracket structure within the specified division.\n\n**What this endpoint does:**\n1. Validates that the division exists and belongs to the specified tournament.\n2. Creates two new brackets inside the division: **Upper Bracket** (sequence brackets+1) and **Lower Bracket** (sequence brackets+2).\n3. Generates all matches for both the winner's (upper) and loser's (lower) brackets, plus Grand Finals matches.\n4. Creates a `matchResults` row for each match with default scores of 0.\n\n**Bracket structure:**\n- **Upper Bracket:** Contains the winner's bracket rounds plus the Grand Finals and Grand Finals (if necessary) matches. Match types: \"Winner's Round N\", \"Winner's Quarter Finals\", \"Winner's Semi Finals\", \"Winner's Finals\", \"Grand Finals\", \"Grand Finals (if necessary)\".\n- **Lower Bracket:** Contains all loser's bracket rounds. Match types: \"Loser's Round N\", \"Loser's Quarter Finals\", \"Loser's Semi Finals\", \"Loser's Finals\".\n- Winner's bracket matches have `loserMatchGuid` set, pointing to the corresponding loser bracket match that the loser drops into.\n- Lower bracket matches have `loserMatchGuid: null` (losers are fully eliminated).\n- Grand Finals has `nextMatchGuid` pointing to the \"Grand Finals (if necessary)\" match.\n- For non-power-of-2 team counts, the first round of the winner's bracket should be partially populated with byes.\n\n**Permissions required:** Tournament organiser, admin, staff, head coordinator, or a global tournament management role.\n\n\nRequired token scope: `rest:write`.","tags":["Brackets"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"},"description":"The tournament's GUID or human-readable ID"},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string","format":"uuid"},"description":"The GUID of the division in which to create the brackets. All existing brackets in this division will be deleted first."},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer","minimum":2},"description":"The number of teams participating. Must be at least 2. Determines the bracket size, number of winner/loser rounds, and total match count."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"assignTeams":{"type":"boolean","default":true,"description":"Assign seeded teams automatically. Set false to leave all team slots empty."},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"}}}}}},"responses":{"200":{"description":"Double elimination brackets generated successfully. Returns both created brackets and all matches.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully generated the double elimination bracket!"},"upperBracket":{"type":"object","description":"The newly created Upper Bracket row","properties":{"guid":{"type":"string","format":"uuid"},"divisionGuid":{"type":"string","format":"uuid"},"id":{"type":"string","example":"upper-bracket"},"name":{"type":"string","example":"Upper Bracket"},"sequence":{"type":"integer","example":1},"createdAt":{"type":"string","format":"date-time"}}},"lowerBracket":{"type":"object","description":"The newly created Lower Bracket row","properties":{"guid":{"type":"string","format":"uuid"},"divisionGuid":{"type":"string","format":"uuid"},"id":{"type":"string","example":"lower-bracket"},"name":{"type":"string","example":"Lower Bracket"},"sequence":{"type":"integer","example":2},"createdAt":{"type":"string","format":"date-time"}}},"newMatches":{"type":"array","description":"All generated matches across both brackets in insertion order (topologically sorted — later-round matches first)","items":{"type":"object","properties":{"guid":{"type":"string","format":"uuid"},"tournamentGuid":{"type":"string","format":"uuid"},"bracketGuid":{"type":"string","format":"uuid","description":"GUID of the Upper or Lower bracket this match belongs to"},"status":{"type":"string","example":"scheduled"},"matchNumber":{"type":"integer","description":"Display-only match number"},"matchType":{"type":"string","description":"Describes the match position. Possible values include:\n\"Winner's Round N\", \"Winner's Quarter Finals\", \"Winner's Semi Finals\", \"Winner's Finals\",\n\"Loser's Round N\", \"Loser's Quarter Finals\", \"Loser's Semi Finals\", \"Loser's Finals\",\n\"Grand Finals\", \"Grand Finals (if necessary)\"\n"},"nextMatchGuid":{"type":"string","format":"uuid","nullable":true,"description":"GUID of the match the winner advances to. Null for the last Grand Finals match. Can cross brackets (e.g. Loser's Finals → Grand Finals in upper bracket)."},"loserMatchGuid":{"type":"string","format":"uuid","nullable":true,"description":"GUID of the lower bracket match the loser drops into. Only set on winner's bracket matches. Null for lower bracket matches and Grand Finals."},"results":{"type":"string","format":"uuid","description":"GUID of the associated match_results row"}}}}}}}}},"400":{"description":"Missing or invalid required parameters","content":{"text/plain":{"schema":{"type":"string"},"example":"Missing required parameters"}}},"401":{"description":"Unauthorized — invalid token or insufficient permissions","content":{"text/plain":{"schema":{"type":"string"},"example":"Unauthorized!"}}},"404":{"description":"Tournament, user, or division not found","content":{"text/plain":{"schema":{"type":"string"},"example":"Division not found or does not belong to this tournament!"}}},"500":{"description":"Internal server error during bracket generation","content":{"text/plain":{"schema":{"type":"string"},"example":"Failed to generate bracket"}}}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generateSwiss/{teamAmount}":{"post":{"summary":"Generate a swiss bracket","tags":["Bracket Generation"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"winsRequired":{"type":"integer","minimum":1,"maximum":8,"default":3},"lossesRequired":{"type":"integer","minimum":1,"maximum":8,"description":"Defaults to winsRequired."},"swissMode":{"type":"string","enum":["thresholds","fixed_rounds"],"default":"thresholds"},"roundCount":{"type":"integer","minimum":1,"maximum":16,"description":"Required when swissMode is fixed_rounds."},"advancingTeamCount":{"type":"integer","minimum":1,"description":"Number of teams shown above the advancement cut after the final fixed round."},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"},"assignTeams":{"type":"boolean","default":true,"description":"Assign seeded teams automatically. Set false to leave all team slots empty."}}}}}},"responses":{"200":{"description":"Swiss bracket generated"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to generate bracket"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generateBattleRoyale/{teamAmount}":{"post":{"summary":"Generate a battle royale bracket","tags":["Bracket Generation"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"assignTeams":{"type":"boolean","default":true,"description":"Assign seeded teams automatically. Set false to leave all team slots empty."},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"},"roundCount":{"type":"integer"},"teamsPerMatch":{"type":"integer"}}}}}},"responses":{"200":{"description":"Battle royale bracket generated"},"401":{"description":"Unauthorized"},"500":{"description":"Failed to generate bracket"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/division/{divisionGuid}/generateRoundRobin/{teamAmount}":{"post":{"summary":"Generate a single round robin (every pair meets once)","tags":["Bracket Generation"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"divisionGuid","required":true,"schema":{"type":"string"}},{"in":"path","name":"teamAmount","required":true,"schema":{"type":"integer","minimum":2,"maximum":128}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"assignTeams":{"type":"boolean","default":true},"seedingStrategy":{"type":"string","enum":["high_low","adjacent"],"default":"high_low"}}}}}},"responses":{"200":{"description":"Round robin generated"},"400":{"description":"Invalid team count or assignment option"},"401":{"description":"Unauthorized"},"500":{"description":"Generation failed"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/tournaments/{tournamentIdentifier}/brackets/{bracketGuid}/convertSwiss":{"post":{"summary":"Convert an unplayed legacy round schedule into record-based Swiss","description":"Preserves opening teams, existing match IDs and schedules. Resets future pairings. Refuses brackets with played results.\n\nRequired token scope: `rest:write`.","tags":["Bracket Generation"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"bracketGuid","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamAmount"],"properties":{"teamAmount":{"type":"integer","minimum":2,"maximum":128},"winsRequired":{"type":"integer","minimum":1,"maximum":8,"default":3},"lossesRequired":{"type":"integer","minimum":1,"maximum":8,"default":3}}}}}},"responses":{"200":{"description":"Converted without deleting existing matches"},"400":{"description":"Invalid configuration"},"401":{"description":"Unauthorized"},"404":{"description":"Bracket not found"},"409":{"description":"Existing results prevent conversion"}},"x-required-scopes":["rest:write"]}},"/badgeProviders":{"get":{"summary":"List public badge providers and their requirements","tags":["Badges"],"responses":{"200":{"description":"Public provider list"}},"parameters":[]}},"/badge-recipients/resolve/{identifier}":{"get":{"summary":"Resolve any identifier to a BeatKhana user or an unresolved raw recipient","tags":["Badges"],"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Canonical recipient and optional public BeatKhana user preview"}}}},"/badgeProviders/mine":{"get":{"summary":"List providers the authenticated user reviews or administers","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Provider memberships"},"401":{"description":"Authentication required"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/badgeProviders/{contextId}":{"get":{"summary":"Get a public badge provider","tags":["Badges"],"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Public provider"},"404":{"description":"Provider not found"}}},"put":{"summary":"Update provider public details","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Provider updated"},"403":{"description":"Provider administrator required"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/admin/badgeProviders":{"post":{"summary":"Create a badge provider","tags":["Badges","Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Provider created"},"403":{"description":"Owner or developer required"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/admin/badgeProviders/{contextId}":{"delete":{"summary":"Permanently delete a badge provider","tags":["Badges","Administration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"204":{"description":"Provider deleted"},"403":{"description":"Owner or developer required"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/settings":{"get":{"summary":"Get private provider settings, members, and requirements","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Private provider settings"},"403":{"description":"Provider administrator required"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"put":{"summary":"Update provider webhook settings","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Settings updated"},"403":{"description":"Provider administrator required"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/members":{"post":{"summary":"Add or update a provider reviewer or administrator","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Provider member saved"},"403":{"description":"Provider administrator required"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/members/{userGuid}":{"delete":{"summary":"Remove a provider member","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"204":{"description":"Provider member removed"},"403":{"description":"Provider administrator required"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"userGuid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/requirements":{"post":{"summary":"Add a provider badge requirement","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Requirement created"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/requirements/{guid}":{"put":{"summary":"Update a provider badge requirement","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Requirement updated"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."},"delete":{"summary":"Delete a provider badge requirement","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"204":{"description":"Requirement removed"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badge-requests":{"get":{"summary":"List badge requests accessible to the authenticated user","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Badge requests"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."},"post":{"summary":"Submit a tournament or external badge request","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"Badge request submitted"},"403":{"description":"Tournament management access required"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badge-requests/{guid}":{"get":{"summary":"Get a badge request with all immutable versions and provider reviews","description":"Available to the applicant, authorized tournament staff, and global badge administrators. Returns provider decisions visible to the applicant, but does not grant webhook replay permission.\n\nRequired token scope: `rest:read`.","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Badge request details"},"403":{"description":"Request access denied"}},"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:read"]}},"/badge-requests/{guid}/withdraw":{"post":{"summary":"Withdraw a request before any provider accepts it","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Badge request withdrawn"}},"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badge-requests/{guid}/iterations":{"post":{"summary":"Submit a new immutable iteration of a badge request","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"201":{"description":"New iteration submitted"}},"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/requests":{"get":{"summary":"List a provider review queue and accepted history","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Provider badge requests"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/badgeProviders/{contextId}/requests/{guid}":{"get":{"summary":"Get one request scoped to versions and finals visible to a provider","description":"Requires reviewer or administrator access to this provider. Versions, decisions, finals, and history are scoped to this provider and never include another provider's activity.\n\nRequired token scope: `rest:read`.","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Provider-scoped badge request"},"404":{"description":"Request unavailable to this provider"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:read"]}},"/badgeProviders/{contextId}/requests/{guid}/review":{"post":{"summary":"Accept, reject, request changes, or permanently block a badge request","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Provider review recorded"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/requests/{guid}/final":{"post":{"summary":"Replace the published images for a provider's accepted version","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Provider final images replaced"},"409":{"description":"The provider has not accepted a version"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/badgeProviders/{contextId}/requests/{guid}/badges/{badgeGuid}/download":{"get":{"summary":"Download a provider's published badge file","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Badge image file"},"404":{"description":"Published provider badge not found"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}},{"in":"path","name":"guid","required":true,"schema":{"type":"string"}},{"in":"path","name":"badgeGuid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/badge-request-events/{guid}/replay":{"post":{"summary":"Replay a persisted badge request action to its provider webhooks","description":"Requires administrator access to the provider that owns the event, or global badge administrator access. Applicants, tournament staff, and provider reviewers cannot replay webhook deliveries.\n\nRequired token scope: `rest:write`.","tags":["Badges"],"security":[{"BeatKhanaAuth":["rest:write"]}],"responses":{"200":{"description":"Webhook action replayed"},"403":{"description":"Replay access denied"}},"parameters":[{"in":"path","name":"guid","required":true,"schema":{"type":"string"}}],"x-required-scopes":["rest:write"]}},"/badges/context/{contextId}":{"get":{"summary":"List all published badges for a provider context","tags":["Badges"],"responses":{"200":{"description":"Published badges"}},"parameters":[{"in":"path","name":"contextId","required":true,"schema":{"type":"string"}}]}},"/badges/user/{identifier}":{"get":{"summary":"Get badges for any BeatKhana, Discord, BeatLeader, or ScoreSaber identifier","tags":["Badges"],"responses":{"200":{"description":"Badge array, empty when the identifier is unknown"}},"parameters":[{"in":"path","name":"identifier","required":true,"schema":{"type":"string"}}]}},"/userBadges/{userIdentifier}":{"get":{"summary":"Get assigned badges using the legacy user badge path","tags":["Badges"],"responses":{"200":{"description":"Badge array, empty when the identifier is unknown"}},"parameters":[{"in":"path","name":"userIdentifier","required":true,"schema":{"type":"string"}}]}},"/uploadNomination":{"post":{"summary":"Submit the authenticated user's awards nomination","tags":["Awards"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"}}}}},"responses":{"200":{"description":"Nomination submitted"},"400":{"description":"Missing authentication or nomination already submitted"},"401":{"description":"Invalid authentication"},"403":{"description":"Nominations are closed"},"500":{"description":"Nomination could not be stored"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/requestMyNomination":{"get":{"summary":"Get the authenticated user's awards nomination","tags":["Awards"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Nomination state and optional nomination data"},"400":{"description":"Missing authentication"},"401":{"description":"Invalid authentication"},"500":{"description":"Nomination could not be loaded"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/votes/tallied":{"get":{"summary":"Get aggregate awards voting results","tags":["Awards"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Per-category vote totals and ballot completion statistics"},"400":{"description":"Missing authentication"},"401":{"description":"Invalid authentication or insufficient permissions"},"500":{"description":"Votes could not be tallied"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/uploadVote":{"post":{"summary":"Submit unfilled categories on the authenticated user's awards ballot","tags":["Awards"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"integer","nullable":true}}}}},"responses":{"200":{"description":"Vote categories submitted"},"400":{"description":"Missing authentication, locked categories, or no new votes"},"401":{"description":"Invalid authentication"},"403":{"description":"Voting is closed"},"500":{"description":"Vote could not be stored"}},"parameters":[],"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/requestMyVote":{"get":{"summary":"Get the authenticated user's awards ballot","tags":["Awards"],"security":[{"BeatKhanaAuth":["rest:read"]}],"responses":{"200":{"description":"Voting state and optional ballot data"},"400":{"description":"Missing authentication"},"401":{"description":"Invalid authentication"},"500":{"description":"Ballot could not be loaded"}},"parameters":[],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/tournaments/{tournamentIdentifier}/availability":{"get":{"summary":"List tournament availability sheets visible to the current user","description":"Returns sheets with joined targets, responses, response users, match assignments, brackets, and divisions. Availability is private to authenticated tournament staff and applicants; sheets outside the caller's tournament access are omitted. The role arrays describe which sheets the caller can answer and which current staffing roles they can manage.\n\nRequired token scope: `rest:read`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Visible availability state, including fully joined sheet data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityState"}}}},"401":{"description":"Missing, invalid, or expired BeatKhana bearer token.","content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"Tournament or availability data could not be loaded.","content":{"text/plain":{"schema":{"type":"string"}}}}},"x-required-scopes":["rest:read"]},"post":{"summary":"Create an availability sheet","description":"Creates a role-restricted sheet and its match or day targets atomically. Organisers, administrators, staff, head coordinators, and head casters may create sheets. The API stores tournament role enum values without limiting sheets to the four staffing roles currently presented by the UI. Every selected match must belong to the tournament.\n\nRequired token scope: `rest:write`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilitySheetInput"},"examples":{"matches":{"summary":"Selected matches for coordinators and referees","value":{"title":"Grand Finals Availability","mode":"matches","roles":["role:coordinator","role:referee"],"timezone":"Europe/Budapest","matchGuids":["03b8db5e-33d3-4575-94c4-27816595330f"],"closesAt":"2026-08-20T18:00:00.000Z"}},"range":{"summary":"Continuous casting window","value":{"title":"Finals Weekend","mode":"range","roles":["role:caster"],"timezone":"America/New_York","startsAt":"2026-08-22T14:00:00.000Z","endsAt":"2026-08-23T23:00:00.000Z"}}}}}},"responses":{"201":{"description":"Sheet created with its joined targets.","content":{"application/json":{"schema":{"type":"object","required":["sheet"],"properties":{"sheet":{"$ref":"#/components/schemas/AvailabilitySheet"}}}}}},"400":{"description":"Missing title/roles/targets","invalid range":null,"or a match belongs to another tournament":null},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller is not permitted to create availability sheets"},"500":{"description":"The transaction could not create the sheet"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/availability/{sheetGuid}":{"get":{"summary":"Get one availability sheet","description":"Loads one sheet in the requested tournament with its creator, UTC day windows, selected and fully joined matches, all authorized responses, and the caller's applicable roles. The sheet is not exposed to public viewers or unrelated tournament members.\n\nRequired token scope: `rest:read`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}},{"in":"path","name":"sheetGuid","required":true,"description":"Availability sheet UUID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Joined sheet and caller role context.","content":{"application/json":{"schema":{"type":"object","required":["sheet","applicantRoles","manageableRoles","userGuid"],"properties":{"sheet":{"$ref":"#/components/schemas/AvailabilitySheet"},"applicantRoles":{"type":"array","items":{"type":"string"}},"manageableRoles":{"type":"array","items":{"type":"string"}},"userGuid":{"type":"string","format":"uuid"}}}}}},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller is not tournament staff or an applicant for the sheet"},"404":{"description":"The sheet does not exist in this tournament"},"500":{"description":"The sheet could not be loaded"}},"x-required-scopes":["rest:read"]},"put":{"summary":"Replace an availability sheet's configuration and targets","description":"Replaces editable sheet fields plus selected matches or day windows in one transaction. Responses that still fit the new targets are retained; only responses outside the new mode, matches, days, or range are pruned. Tournament roles remain extensible at the API and database layer.\n\nRequired token scope: `rest:write`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}},{"in":"path","name":"sheetGuid","required":true,"description":"Availability sheet UUID.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilitySheetInput"}}}},"responses":{"200":{"description":"Updated, fully joined sheet.","content":{"application/json":{"schema":{"type":"object","required":["sheet"],"properties":{"sheet":{"$ref":"#/components/schemas/AvailabilitySheet"}}}}}},"400":{"description":"Invalid sheet fields/targets or a selected match belongs to another tournament"},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller cannot manage availability sheets"},"404":{"description":"The sheet does not exist in this tournament"},"500":{"description":"The sheet transaction could not be completed"}},"x-required-scopes":["rest:write"]},"delete":{"summary":"Delete an availability sheet","description":"Permanently deletes the sheet. Selected matches, day windows, and all applicant responses are removed by cascading foreign keys. Only tournament availability managers may delete sheets.\n\nRequired token scope: `rest:write`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}},{"in":"path","name":"sheetGuid","required":true,"description":"Availability sheet UUID.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sheet and associated data deleted.","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"string","example":"Availability sheet deleted."}}}}}},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller cannot manage availability sheets"},"404":{"description":"The sheet does not exist in this tournament"},"500":{"description":"The sheet could not be deleted"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/availability/{sheetGuid}/responses":{"put":{"summary":"Replace the current user's responses for one role","description":"Atomically replaces only the authenticated user's responses for the supplied sheet and tournament role. Other users and the caller's responses under other roles remain unchanged. Calendar intervals must align to 15-minute boundaries and are stored using the sheet's selected 15-, 30-, or 60-minute slots; if submitted intervals overlap, the later item in the request overwrites the earlier item for that slot. Match responses must reference a selected match. Sending an empty responses array clears the caller's availability for that role.\n\nRequired token scope: `rest:write`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}},{"in":"path","name":"sheetGuid","required":true,"description":"Availability sheet UUID.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role","responses"],"properties":{"role":{"type":"string","example":"role:coordinator","description":"The tournament role being answered as; the caller must hold it and the sheet must allow it."},"responses":{"type":"array","description":"Complete replacement response set for this user and role.","items":{"$ref":"#/components/schemas/AvailabilityResponseInput"}}}},"examples":{"matchResponse":{"value":{"role":"role:referee","responses":[{"matchGuid":"03b8db5e-33d3-4575-94c4-27816595330f","status":"would_rather_not"}]}},"calendarIntervals":{"value":{"role":"role:caster","responses":[{"startsAt":"2026-08-22T14:00:00.000Z","endsAt":"2026-08-22T17:30:00.000Z","status":"available"}]}}}}}},"responses":{"200":{"description":"Responses saved; returns the refreshed joined sheet.","content":{"application/json":{"schema":{"type":"object","required":["sheet"],"properties":{"sheet":{"$ref":"#/components/schemas/AvailabilitySheet"}}}}}},"400":{"description":"Invalid or disallowed status","non-15-minute calendar boundaries":null,"or response target falls outside the sheet":null},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller does not hold the supplied role or the sheet does not accept it"},"404":{"description":"The sheet does not exist in this tournament"},"409":{"description":"The sheet's response deadline has passed"},"500":{"description":"Responses could not be saved"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/availability/matches/{matchGuid}/assign":{"post":{"summary":"Directly assign an applicant to a match","description":"Assigns a user as coordinator, referee, caster, or stream host without opening match settings. The caller must manage that staffing role, the user must hold the corresponding tournament role, and the match must belong to the tournament. The role's specific-assignment switch is enabled automatically. Repeating the same request is idempotent.\n\nRequired token scope: `rest:write`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"description":"Tournament slug or UUID.","schema":{"type":"string"}},{"in":"path","name":"matchGuid","required":true,"description":"Match UUID belonging to this tournament.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userGuid","role"],"properties":{"userGuid":{"type":"string","format":"uuid","description":"Applicant to assign."},"role":{"type":"string","enum":["role:coordinator","role:referee","role:caster","role:host"]}}},"example":{"userGuid":"c365a910-7646-45e5-b26c-1e37908e10f7","role":"role:coordinator"}}}},"responses":{"200":{"description":"User assigned, or already assigned, and the assignment gate enabled.","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"string","example":"Luna assigned."}}}}}},"400":{"description":"The target user does not hold the requested tournament role"},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller cannot assign the requested staffing role"},"404":{"description":"Match or user not found"},"500":{"description":"The match assignment could not be saved"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/availability/matches/{matchGuid}/unassign":{"post":{"summary":"Remove a staff assignment from a match","description":"Removes a coordinator, referee, caster, or stream host from a match. The caller must manage that staffing role. When the last person of a role is removed, the role's specific-assignment switch is turned off so the role is open to all of its staff again. Repeating the request is idempotent.\n\nRequired token scope: `rest:write`.","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"matchGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userGuid","role"],"properties":{"userGuid":{"type":"string","format":"uuid"},"role":{"type":"string","enum":["role:coordinator","role:referee","role:caster","role:host"]}}}}}},"responses":{"200":{"description":"User removed from the match, or was not assigned","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"string"}}}}}},"401":{"description":"Missing","invalid":null,"or expired bearer token":null},"403":{"description":"The caller cannot manage the requested staffing role"},"404":{"description":"Match or user not found"},"500":{"description":"The assignment could not be removed"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/availability/{sheetGuid}/team-responses":{"put":{"summary":"Save the authenticated captain's team availability","tags":["Availability"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"path","name":"sheetGuid","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["teamGuid","responses"],"properties":{"teamGuid":{"type":"string","format":"uuid"},"responses":{"type":"array","maxItems":5000,"items":{"$ref":"#/components/schemas/TeamAvailabilityResponseInput"}}}}}}},"responses":{"200":{"description":"Team answers saved; response includes the sheet","content":{"application/json":{"schema":{"type":"object","required":["sheet"],"properties":{"sheet":{"$ref":"#/components/schemas/AvailabilitySheet"}}}}}},"400":{"description":"Invalid or disallowed answers or times"},"401":{"description":"Authentication required"},"403":{"description":"Only an eligible team's captain may answer"},"404":{"description":"Sheet not found"},"409":{"description":"Sheet closed"},"500":{"description":"Responses could not be saved"}},"x-required-scopes":["rest:write"],"description":"Required token scope: `rest:write`."}},"/requestAuthToken":{"get":{"summary":"Removed legacy Discord-token exchange","description":"This endpoint no longer accepts browser-provided Discord tokens. Start the OAuth 2.0 authorization-code flow at `/oauth/authorize`.\n\nRequired token scope: `rest:read`.","tags":["Authentication"],"parameters":[{"in":"header","name":"authorization","required":true,"description":"Discord OAuth bearer token","schema":{"type":"string","example":"Bearer <discord-oauth-token>"}},{"in":"query","name":"scopes","required":false,"description":"Comma-separated list of requested scopes. Defaults to all scopes if omitted.","schema":{"type":"string","example":"ws:read,rest:user:read,rest:read"}}],"responses":{"400":{"description":"Missing or invalid authorization header."},"410":{"description":"The legacy token exchange was permanently removed.","content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"discordData":{"type":"object"},"userData":{"type":"object"}}}}}},"500":{"description":"Internal server error."}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/generateTestingAuthToken":{"post":{"summary":"Generate a short-lived auth token for testing as another user","description":"Owner-only testing utility. The caller must authenticate as Luna (Discord ID 469171963236057120). The target must already exist in the BeatKhana database. Generated tokens use the regular BeatKhana JWT format, expire after 24 hours, and are not returned anywhere except this response.\n\n\nRequired token scope: `rest:write`.","tags":["Authentication"],"security":[{"BeatKhanaAuth":["rest:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["discordId"],"properties":{"discordId":{"type":"string","pattern":"^\\\\d{17,20}$","description":"Discord ID of an existing BeatKhana user to impersonate for testing.","example":"123456789012345678"},"scopes":{"type":"array","description":"Optional JWT scopes. All supported scopes are granted when omitted.","items":{"type":"string","enum":["ws:read","ws:write","rest:user:read","rest:user:write","rest:read","rest:write","tournamentassistant","tournamentassistant:game","compcube"]}}}}}}},"responses":{"200":{"description":"A 24-hour BeatKhana auth token for the requested user.","content":{"application/json":{"schema":{"type":"object","required":["token","expiresIn","user"],"properties":{"token":{"type":"string","description":"Signed BeatKhana JWT."},"expiresIn":{"type":"string","example":"24h"},"user":{"type":"object","required":["guid","discordId","username"],"properties":{"guid":{"type":"string","format":"uuid"},"discordId":{"type":"string"},"username":{"type":"string"}}}}}}}},"400":{"description":"Missing or malformed Discord ID, or invalid requested scopes."},"401":{"description":"Missing or invalid BeatKhana authentication token."},"403":{"description":"The authenticated caller is not Luna."},"404":{"description":"No BeatKhana user exists with the supplied Discord ID."},"500":{"description":"Failed to generate the testing token."}},"parameters":[],"x-required-scopes":["rest:write"]}},"/authorizeBeatLeader":{"get":{"summary":"Authorize a user with BeatLeader","description":"Validates the user's BeatKhana token, exchanges a BeatLeader OAuth authorization code for an access token, retrieves user identity + profile, and updates BeatKhana user data.\n\n\nRequired token scope: `rest:user:read`.","tags":["Authentication"],"security":[{"BeatKhanaAuth":["rest:user:read"]}],"parameters":[{"in":"header","name":"authorization","required":true,"description":"BeatKhana auth token","schema":{"type":"string"}},{"in":"header","name":"blToken","required":true,"description":"BeatLeader OAuth authorization code","schema":{"type":"string"}},{"in":"header","name":"blRedirectUri","required":true,"description":"Redirect URI used in BeatLeader authorize step (raw URL or URL-encoded)","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully authorized with BeatLeader.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string"},"tokenData":{"type":"object"},"userData":{"type":"object"}}}}}},"400":{"description":"Missing authorization header, BeatLeader code, or redirect URI."},"401":{"description":"Invalid BeatKhana auth token."},"500":{"description":"Error while processing BeatLeader authorization."}},"x-required-scopes":["rest:user:read"]}},"/authorizeTwitch":{"get":{"summary":"Authorize a user with Twitch","description":"Validates the user's BeatKhana token, exchanges a Twitch OAuth authorization code for an access token, retrieves the Twitch profile, and stores Twitch username.\n\n\nRequired token scope: `rest:user:read`.","tags":["Authentication"],"security":[{"BeatKhanaAuth":["rest:user:read"]}],"parameters":[{"in":"header","name":"authorization","required":true,"description":"BeatKhana auth token","schema":{"type":"string"}},{"in":"header","name":"twitchToken","required":true,"description":"Twitch OAuth authorization code","schema":{"type":"string"}},{"in":"header","name":"twitchRedirectUri","required":true,"description":"Redirect URI used in the Twitch authorize step","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully authorized with Twitch.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string"},"tokenData":{"type":"object"},"userData":{"type":"object"}}}}}},"400":{"description":"Missing authorization header or Twitch code."},"401":{"description":"Invalid BeatKhana auth token."},"500":{"description":"Error while processing Twitch authorization."}},"x-required-scopes":["rest:user:read"]}},"/authorizeScoreSaber":{"get":{"summary":"Authorize a user with ScoreSaber","description":"Validates the user's BeatKhana token, exchanges a ScoreSaber OAuth authorization code for an access token, retrieves ScoreSaber identity/account-linking data, and updates BeatKhana user data.\n\n\nRequired token scope: `rest:user:read`.","tags":["Authentication"],"security":[{"BeatKhanaAuth":["rest:user:read"]}],"parameters":[{"in":"header","name":"authorization","required":true,"description":"BeatKhana auth token","schema":{"type":"string"}},{"in":"header","name":"ssToken","required":true,"description":"ScoreSaber OAuth authorization code","schema":{"type":"string"}},{"in":"header","name":"ssRedirectUri","required":true,"description":"Redirect URI used in the ScoreSaber authorize step","schema":{"type":"string"}},{"in":"header","name":"ssCodeVerifier","required":false,"description":"PKCE verifier used during ScoreSaber authorization","schema":{"type":"string"}}],"x-required-scopes":["rest:user:read"]}},"/validateMe":{"get":{"summary":"Validate an authorization token using BeatKhana authentication","tags":["Authentication"],"parameters":[{"in":"header","name":"Authorization","required":true,"description":"BeatKhana authentication token","schema":{"type":"string"}}],"responses":{"200":{"description":"Token validated","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Validation response object"}}}},"400":{"description":"Missing Authorization header","content":{"text/plain":{"schema":{"type":"string"},"example":"Please specify a token"}}},"401":{"description":"Invalid token"}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"],"description":"Required token scope: `rest:read`."}},"/requestPublicSignature":{"get":{"summary":"Retrieve the server's public RSA signature key","description":"Returns the public key used for verifying JWT signatures.","tags":["Authentication"],"responses":{"200":{"description":"Successfully returned the public signature key.","content":{"application/json":{"schema":{"type":"object","properties":{"algorithm":{"type":"string","example":"RS256"},"publicKey":{"type":"string"}}}}}},"500":{"description":"Failed to retrieve the public signature key."}},"parameters":[]}},"/game/requestAuthToken":{"post":{"summary":"Exchange a Beat Saber identity proof for an external-service token","description":"Accepts the existing TournamentAssistant ScoreSaber proof or a Steam/Meta PCVR ticket. Tokens are identity assertions for TournamentAssistant and CompCube; BeatKhana deliberately rejects them as API and websocket session credentials. When a platform is linked, the JWT embeds the BeatKhana preferred name in `username` plus the user's `avatarUrl`, matching the claims consumed by the TA server. For compatibility, unlinked TA and CompCube players may authenticate with only a verified platform ID.\n\nRequired token scope: `rest:write`.","servers":[{"url":"https://api.beatkhana.com","description":"Production server (legacy game namespace)"},{"url":"http://localhost:3001","description":"Development server (legacy game namespace)"},{"url":"https://api.staging.beatkhana.com","description":"Staging server (legacy game namespace)"}],"tags":["Game Authentication"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string","enum":["steamTicket","oculusTicket","scoresaber"],"description":"Optional for legacy ScoreSaber requests that provide proofNonce/proofPlayerId."},"ticket":{"type":"string","description":"Steam session ticket or Meta X-platform access token."},"scopes":{"type":"array","items":{"type":"string","enum":["tournamentassistant","tournamentassistant:game","compcube"]},"description":"Defaults to tournamentassistant:game for legacy TA clients."},"platform":{"type":"string","enum":["steam","oculuspc","quest"],"description":"Legacy ScoreSaber field."},"platformId":{"type":"string","description":"Legacy ScoreSaber player ID; ignored for Steam and Meta tickets."},"platformUsername":{"type":"string","description":"Legacy ScoreSaber display name."},"proofProvider":{"type":"string","enum":["scoresaber"],"description":"Legacy provider field."},"proofPlayerId":{"type":"string","description":"Legacy alias for platformId."},"proofNonce":{"type":"string","description":"Legacy ScoreSaber nonce field."}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Signed 24-hour external identity token. Unlinked CompCube players have null userGuid/discordId and a verified platformId.","content":{"application/json":{"schema":{"type":"object","required":["token","expiresInSeconds","scopes","platform","platformId","platformUsername","proofProvider","proofVerified","linkedBeatKhanaUser","userGuid","discordId"],"properties":{"token":{"type":"string","description":"RS256-signed external identity JWT; not a BeatKhana session token. Linked TA tokens embed the BeatKhana preferred name as `username` and the user's `avatarUrl`."},"expiresInSeconds":{"type":"integer","example":86400},"scopes":{"type":"array","items":{"type":"string"},"example":["compcube"]},"platform":{"type":"string","enum":["steam","oculuspc","quest"]},"platformId":{"type":"string","description":"Derived from the verified provider rather than trusting a Steam/Meta request field."},"platformUsername":{"type":"string"},"proofProvider":{"type":"string","enum":["steamTicket","oculusTicket","scoresaber"]},"proofVerified":{"type":"boolean","enum":[true]},"linkedBeatKhanaUser":{"type":"boolean"},"userGuid":{"type":"string","format":"uuid","nullable":true},"discordId":{"type":"string","nullable":true}}}}}},"400":{"description":"Invalid provider","proof fields":null,"scope":null,"or a ScoreSaber request for the PCVR-only CompCube scope.":null},"401":{"description":"Platform proof rejected."},"502":{"description":"Upstream identity provider unavailable or Steam authentication is not configured."}},"parameters":[],"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"]}},"/analytics/events":{"post":{"summary":"Record tournament statistics events","description":"Records page views, time on page, tournament list impressions and clicks, and outbound link clicks. Sent by the BeatKhana website; no authentication is required. A bearer token, when present, links the events to the signed-in user so organisers can see how many visitors were signed in.\n\nRequired token scope: `rest:write`.","tags":["Statistics"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["visitorId","events"],"properties":{"visitorId":{"type":"string","description":"Random browser identifier (8-64 URL-safe characters)"},"sessionId":{"type":"string"},"device":{"type":"string","enum":["desktop","mobile","tablet"]},"referrerHost":{"type":"string"},"events":{"type":"array","maxItems":50,"items":{"type":"object","required":["tournament","type"],"properties":{"tournament":{"type":"string","description":"Tournament GUID or ID"},"type":{"type":"string","enum":["page_view","page_leave","list_impression","list_click","outbound_click"]},"target":{"type":"string","description":"Section name or link host"},"durationMs":{"type":"integer","description":"Time on page for page_leave events"}}}}}}}}},"responses":{"202":{"description":"Events accepted"},"400":{"description":"Invalid batch"},"429":{"description":"Too many events from this visitor"}},"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[],"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentIdentifier}/statistics":{"get":{"summary":"Get tournament statistics","description":"Aggregated views, visitors, list impressions, section popularity, devices, traffic sources and downloads. Requires organiser, admin or staff access.\n\nRequired token scope: `rest:read`.","tags":["Statistics"],"security":[{"BeatKhanaAuth":["rest:read"]}],"parameters":[{"in":"path","name":"tournamentIdentifier","required":true,"schema":{"type":"string"}},{"in":"query","name":"range","required":false,"schema":{"type":"string","enum":["7","30","90","all"],"default":"30"}}],"responses":{"200":{"description":"Statistics summary"},"400":{"description":"Missing authorization"},"401":{"description":"Not permitted to view statistics"},"404":{"description":"Tournament not found"}},"x-required-scopes":["rest:read"]}},"/tournaments/{tournamentId}/additionalFields":{"post":{"summary":"Create or update additional registration fields in bulk","description":"Creates new additional registration fields when guid is omitted, or updates existing ones when guid is provided.\n\nRequired token scope: `rest:write`.","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentId","required":true,"schema":{"type":"string"},"description":"Tournament GUID or ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"additionalRegistrationFields":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Additional registration fields were upserted successfully"},"400":{"description":"Bad request or invalid field data"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"}},"x-required-scopes":["rest:write"]}},"/tournaments/{tournamentId}/additionalFields/{fieldGuid}":{"delete":{"summary":"Delete an additional registration field","description":"Deletes a single additional registration field by its guid.\n\nRequired token scope: `rest:write`.","tags":["Tournament Registration"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[{"in":"path","name":"tournamentId","required":true,"schema":{"type":"string"}},{"in":"path","name":"fieldGuid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Additional registration field deleted"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"}},"x-required-scopes":["rest:write"]}},"/metrics":{"get":{"summary":"Export Prometheus metrics","description":"The path can be overridden with METRICS_PATH. Requires configured bearer, basic, or IP allowlist authentication.","tags":["Monitoring"],"security":[{"BeatKhanaAuth":[]}],"responses":{"200":{"description":"Prometheus text exposition","content":{"text/plain":{}}},"401":{"description":"Unauthorized"},"503":{"description":"Metrics authentication is not configured"}}}},"/api/cdn/view/{filename}":{"get":{"summary":"Redirect legacy CDN view URLs","description":"Redirects the old REST API CDN view endpoint to the canonical CDN view URL.","tags":["CDN"],"parameters":[{"in":"path","name":"filename","required":true,"schema":{"type":"string"}}],"responses":{"308":{"description":"Permanent redirect to the canonical CDN view URL"}}}},"/":{"get":{"summary":"Developer documentation overview","tags":["Documentation"],"responses":{"200":{"description":"Hub page linking every reference","content":{"text/html":{}}}}}},"/docs/openapi.json":{"get":{"summary":"Get the OpenAPI document","tags":["Documentation"],"responses":{"200":{"description":"OpenAPI 3 document (also served at /api/openapi.json)","content":{"application/json":{}}}}}},"/reference":{"get":{"summary":"View the REST reference (Scalar)","tags":["Documentation"],"responses":{"200":{"description":"Scalar API reference","content":{"text/html":{}}}}}},"/reference/scalar.js":{"get":{"summary":"Scalar browser bundle","tags":["Documentation"],"responses":{"200":{"description":"Self-hosted Scalar bundle used by /reference","content":{"application/javascript":{}}}}}},"/socket/docs":{"get":{"summary":"View the Socket.IO packet reference","tags":["Documentation"],"responses":{"200":{"description":"Packet and event reference with a getting-started guide","content":{"text/html":{}}}}}},"/docs-assets/beatkhana-developer.png":{"get":{"summary":"Get the logo shown in the documentation header","tags":["Documentation"],"responses":{"200":{"description":"BeatKhana! Development logo","content":{"image/png":{}}},"404":{"description":"The bundled assets folder is missing"}}}},"/MIGRATE_ADD_OAUTH_APPLICATION_CREATE_PERMISSION":{"post":{"summary":"POST /MIGRATE_ADD_OAUTH_APPLICATION_CREATE_PERMISSION","description":"Auto-documented from src/routes/migration.route.ts.\n\nRequired token scope: `rest:write`.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"]}},"/MIGRATE_CDN_URLS_TO_CDN_HOST":{"post":{"summary":"POST /MIGRATE_CDN_URLS_TO_CDN_HOST","description":"Auto-documented from src/routes/migration.route.ts.\n\nRequired token scope: `rest:write`.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"security":[{"BeatKhanaAuth":["rest:write"]}],"x-required-scopes":["rest:write"]}},"/MIGRATE_DATABASE_MATCH_TEAMS":{"get":{"summary":"GET /MIGRATE_DATABASE_MATCH_TEAMS","description":"Auto-documented from src/routes/migration.route.ts.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/DATABASE_FIX_USER_IMAGES":{"get":{"summary":"GET /DATABASE_FIX_USER_IMAGES","description":"Auto-documented from src/routes/migration.route.ts.\n\nRequired token scope: `rest:read`.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/DATABASE_CLEANUP_IMAGES":{"get":{"summary":"GET /DATABASE_CLEANUP_IMAGES","description":"Auto-documented from src/routes/migration.route.ts.\n\nRequired token scope: `rest:read`.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/DATABASE_SET_CAPTAIN_AS_PLAYER_FIX":{"get":{"summary":"GET /DATABASE_SET_CAPTAIN_AS_PLAYER_FIX","description":"Auto-documented from src/routes/migration.route.ts.\n\nRequired token scope: `rest:read`.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/DATABASE_FETCH_ALL_SET_PFPS":{"get":{"summary":"GET /DATABASE_FETCH_ALL_SET_PFPS","description":"Auto-documented from src/routes/migration.route.ts.\n\nRequired token scope: `rest:read`.","tags":["Internal"],"parameters":[],"responses":{"200":{"description":"Successful response"},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"BeatKhanaAuth":["rest:read"]}],"x-required-scopes":["rest:read"]}},"/streamKeys/create":{"put":{"summary":"Create a stream key","description":"Creates a new stream key for a user within a specific key group and tournament. Supports regular keys and floating keys. Requires tournament staff permissions or global streaming permissions. If tournamentGuid is not provided, requires global permissions.\n\nRequired token scope: `rest:write`.\n\nThe `tournamentGuid` path segment is optional; this form omits it.","tags":["Stream Keys"],"security":[{"BeatKhanaAuth":["rest:write"]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["groupGuid","userIdentifier"],"properties":{"groupGuid":{"type":"string","description":"GUID of the stream key group","example":"group-123"},"userIdentifier":{"type":"string","description":"User GUID or Discord ID","example":"user-456"},"isFloatingKey":{"type":"boolean","description":"When true, key can be used against configured endpoint names instead of user GUID path matching","default":false},"floatingKeyEndpoints":{"type":"array","description":"Optional endpoint allowlist for floating keys. Required when isFloatingKey is true.","items":{"type":"string","example":"castA1"}}}}}}},"responses":{"200":{"description":"Stream key created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","example":"Successfully added stream key!"},"errors":{"type":"array","items":{"type":"string"}},"newKey":{"type":"object"}}}}}},"400":{"description":"Bad request - missing required fields, invalid data, or invalid floating key endpoint configuration","content":{"text/plain":{"schema":{"type":"string"},"example":"Please provide both the groupGuid and the userGuid in the body!"}}},"401":{"description":"Unauthorized - invalid token, insufficient permissions, or tournament mismatch","content":{"text/plain":{"schema":{"type":"string"},"example":"There is a mismatch betweent the key group tournament guid and the provided tournament guid!"}}},"404":{"description":"User not found","content":{"text/plain":{"schema":{"type":"string"},"example":"The user account of the !"}}},"500":{"description":"Internal server error","content":{"text/plain":{"schema":{"type":"string"},"example":"Something went wrong while fetching the user profile to which the key should be added"}}}},"x-required-scopes":["rest:write"]}}},"tags":[{"name":"Authentication","description":"Discord sign-in, token refresh and linking BeatLeader, ScoreSaber and Twitch."},{"name":"OAuth 2.0","description":"Authorization-code flow (with optional PKCE) for third-party applications."},{"name":"OAuth Applications","description":"Register and manage your OAuth clients."},{"name":"Users","description":"Profiles, hover cards and the signed-in user's account."},{"name":"Email Verification"},{"name":"Tournaments","description":"Discovery, details and lifecycle."},{"name":"Tournament Settings"},{"name":"Tournament Visibility","description":"Requesting and reviewing public listing on BeatKhana."},{"name":"Tournament Platforms","description":"Per-platform visibility review and webhooks."},{"name":"Tournament Organisers"},{"name":"Tournament Permissions"},{"name":"Tournament Registration"},{"name":"Tournament Qualifiers"},{"name":"Tournament Timeline Events"},{"name":"Staff"},{"name":"Availability"},{"name":"Teams"},{"name":"Statistics"},{"name":"Donations"},{"name":"Divisions"},{"name":"Brackets"},{"name":"Bracket Generation"},{"name":"Matches","description":"Match records, schedules and results. Live coordination happens over Socket.IO (`match:*`)."},{"name":"Picks and Bans"},{"name":"Maps"},{"name":"Map Pooling"},{"name":"Pools"},{"name":"Pooling Resources"},{"name":"Map Analysis"},{"name":"Overlays","description":"Overlay configuration, custom packets and saved states. Live editing happens over Socket.IO."},{"name":"Streaming"},{"name":"Stream Keys","description":"WHIP publishing credentials for players and broadcasters."},{"name":"CDN","description":"Uploads and media delivery."},{"name":"Embeds"},{"name":"Communities"},{"name":"Calendar"},{"name":"Events"},{"name":"Badges"},{"name":"Awards"},{"name":"Webhooks"},{"name":"Shared Content","description":"Live co-edited docs and sheets, and Google-Forms-style forms. Text and cells are edited through the `doc`, `sheet` and `form` Socket.IO collaboration scopes; these endpoints create, configure, share, answer and export them."},{"name":"Discord Integration"},{"name":"Discord"},{"name":"Game"},{"name":"Game Authentication"},{"name":"Administration"},{"name":"Monitoring","description":"Prometheus metrics (protected)."},{"name":"Documentation"},{"name":"Internal","description":"One-off maintenance and migration endpoints for platform operators. Not for integrations."},{"name":"matches"}],"externalDocs":{"description":"Developer guides","url":"https://wiki.beatkhana.com/docs/developers"},"x-tagGroups":[{"name":"Accounts and access","tags":["Authentication","OAuth 2.0","OAuth Applications","Users","Email Verification"]},{"name":"Tournaments","tags":["Tournaments","Tournament Settings","Tournament Visibility","Tournament Platforms","Tournament Organisers","Tournament Permissions","Tournament Registration","Tournament Qualifiers","Tournament Timeline Events","Staff","Availability","Teams","Statistics","Donations"]},{"name":"Competition","tags":["Divisions","Brackets","Bracket Generation","Matches","Picks and Bans"]},{"name":"Maps and pools","tags":["Maps","Map Pooling","Pools","Pooling Resources","Map Analysis"]},{"name":"Broadcast and media","tags":["Overlays","Streaming","Stream Keys","CDN","Embeds"]},{"name":"Community","tags":["Communities","Calendar","Events","Badges","Awards","Webhooks"]},{"name":"Docs, sheets and forms","tags":["Shared Content"]},{"name":"Integrations","tags":["Discord Integration","Discord","Game","Game Authentication"]},{"name":"Platform","tags":["Administration","Monitoring","Documentation","Internal"]},{"name":"Other","tags":["matches"]}]}