On this page
- Conventions
- Health & Status
- Games
- Discord Channels
- Settings
- Welcome Flow
- Upload
- Matches
- GET /api/matches
- POST /api/matches
- GET /api/matches/{matchId}
- PUT /api/matches/{matchId}
- DELETE /api/matches/{matchId}
- GET /api/matches/{matchId}/participants
- GET /api/matches/{matchId}/games
- GET /api/matches/{matchId}/games-with-results
- POST /api/matches/{matchId}/assign-teams
- GET /api/matches/{matchId}/overall-score
- POST /api/matches/{matchId}/transition
- GET /api/matches/{matchId}/reminders
- POST /api/matches/{matchId}/map-codes
- GET /api/matches/{matchId}/map-codes
- POST /api/matches/{matchId}/map-notes
- GET /api/matches/{matchId}/map-notes
- GET /api/matches/{matchId}/games/{gameId}/result
- POST /api/matches/{matchId}/games/{gameId}/result
- Tournaments
- GET /api/tournaments
- POST /api/tournaments
- GET /api/tournaments/{tournamentId}
- DELETE /api/tournaments/{tournamentId}
- GET /api/tournaments/{tournamentId}/participants
- GET /api/tournaments/{tournamentId}/teams
- POST /api/tournaments/{tournamentId}/teams
- PUT /api/tournaments/{tournamentId}/teams
- DELETE /api/tournaments/{tournamentId}/teams
- GET /api/tournaments/{tournamentId}/matches
- GET /api/tournaments/{tournamentId}/standings
- POST /api/tournaments/{tournamentId}/transition
- POST /api/tournaments/{tournamentId}/bracket-assignments
- POST /api/tournaments/{tournamentId}/generate-matches
- POST /api/tournaments/{tournamentId}/progress
API Reference
MatchExec API Reference
REST API for the MatchExec match and tournament management system. Last Updated for 0.7.0 Release
Base URL: All endpoints are relative to the application root (e.g., http://localhost:3000).
Content-Type: application/json for all request/response bodies unless noted otherwise.
Conventions
Request Format
- Request bodies use
application/jsonunless the endpoint accepts file uploads (multipart/form-data). - Path parameters are shown as
{paramName}. - Fields marked (required) must be present; all others are optional.
Response Format
- Successful responses return the resource or
{ "success": true }. - Error responses include an
"error"string describing the problem. - Timestamps are ISO 8601 strings (e.g.,
"2024-12-31T23:59:59.999Z"). - Boolean fields from SQLite are converted to native JSON booleans.
Health & Status
GET /api/health
Check whether the application and database are operational.
Response
{
"status": "healthy",
"timestamp": "2024-12-31T23:59:59.999Z",
"services": {
"database": "up",
"web": "up"
}
}| Status Code | Meaning |
|---|---|
| 200 | Service is healthy |
| 503 | Service unavailable — database connection failed |
GET /api/stats
Get system-wide statistics.
Response
{
"totalMatches": 42,
"totalTournaments": 5,
"totalSignups": 180
}| Status Code | Meaning |
|---|---|
| 200 | Success |
GET /api/version
Get application version information.
Response
{
"version": "1.2.3",
"branch": "main",
"commitHash": "abc1234",
"isDev": false
}| Status Code | Meaning |
|---|---|
| 200 | Success |
GET /api/db-status
Get database migration and seeding status. Used by the loading screen UI to track startup progress.
Response
{
"ready": true,
"progress": "Seeding complete",
"timestamp": 1735689599000
}| Status Code | Meaning |
|---|---|
| 200 | Success |
Games
GET /api/games
List all supported games with mode and map counts.
Response
[
{
"id": "overwatch-2",
"name": "Overwatch 2",
"genre": "Hero Shooter",
"developer": "Blizzard Entertainment",
"description": "...",
"minPlayers": 2,
"maxPlayers": 12,
"supportsAllModes": false,
"iconUrl": "/games/overwatch-2/icon.png",
"coverUrl": "/games/overwatch-2/cover.jpg",
"color": "#F99E1A",
"mapCount": 24,
"modeCount": 4
}
]| Status Code | Meaning |
|---|---|
| 200 | Success |
GET /api/games/{gameId}
Get details for a specific game.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
gameId | string | Game identifier |
Response
Same shape as a single item from GET /api/games.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Game not found |
GET /api/games/{gameId}/modes
List all game modes for a game.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
gameId | string | Game identifier |
Response
[
{
"id": "competitive",
"name": "Competitive",
"description": "Ranked play",
"team_size": 5,
"max_players": 10
}
]| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Game not found |
GET /api/games/{gameId}/modes/{modeId}
Get a specific game mode.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
gameId | string | Game identifier |
modeId | string | Mode identifier |
Response
Single mode object with all fields from the game’s modes.json.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Mode not found in game |
GET /api/games/{gameId}/modes/{modeId}/maps
List all maps available for a specific mode.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
gameId | string | Game identifier |
modeId | string | Mode identifier |
Response
[
{
"id": "king-s-row",
"name": "King's Row",
"modeId": "escort",
"imageUrl": "/games/overwatch-2/maps/kings-row.jpg"
}
]| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Game or mode not found |
GET /api/games/{gameId}/maps
List all maps for a game across all modes. CS2 and games with supportsAllModes = true receive deduplicated results with additional fields.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
gameId | string | Game identifier |
Response
[
{
"id": "dust2",
"name": "Dust II",
"modeId": "competitive",
"imageUrl": "/games/cs2/maps/dust2.jpg",
"location": "Middle East",
"modeName": "Competitive",
"modeDescription": "...",
"supportedModes": "competitive,deathmatch"
}
]Notes
- The
supportedModesfield is only present for CS2 games. - Maps are deduplicated when
supportsAllModes = true.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Game not found |
Discord Channels
GET /api/channels
List all Discord channels registered in the system.
Response
[
{
"id": "1",
"discord_channel_id": "123456789012345678",
"channel_name": "#announcements",
"channel_type": "text",
"send_announcements": true,
"send_reminders": false,
"send_match_start": true,
"send_signup_updates": false,
"send_health_alerts": false,
"last_name_refresh": "2024-12-31T23:59:59.999Z",
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-12-31T23:59:59.999Z"
}
]| Status Code | Meaning |
|---|---|
| 200 | Success |
POST /api/channels
Register a new Discord channel.
Request Body
{
"discord_channel_id": "123456789012345678",
"channel_type": "text",
"send_announcements": false,
"send_reminders": false,
"send_match_start": false,
"send_signup_updates": false,
"send_health_alerts": false
}| Field | Type | Required | Description |
|---|---|---|---|
discord_channel_id | string | Yes | Discord channel snowflake ID |
channel_type | "text" | "voice" | Yes | Channel type |
send_announcements | boolean | No | Receive match/tournament announcements |
send_reminders | boolean | No | Receive player reminders |
send_match_start | boolean | No | Receive match start notifications |
send_signup_updates | boolean | No | Receive signup update notifications |
send_health_alerts | boolean | No | Receive health alert notifications |
Response
{
"success": true,
"id": "2",
"message": "Channel created successfully"
}| Status Code | Meaning |
|---|---|
| 200 | Channel created |
| 400 | Validation error |
| 409 | Channel already registered |
PUT /api/channels/{channelId}
Update notification settings for a registered channel.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
channelId | string | Internal channel ID |
Request Body
{
"send_announcements": true,
"send_reminders": true,
"send_match_start": false,
"send_signup_updates": false,
"send_health_alerts": false
}All fields are optional; omitted fields remain unchanged.
Response
{
"success": true,
"message": "Channel notification settings updated successfully"
}| Status Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Voice channels cannot have notification settings modified manually |
| 404 | Channel not found |
DELETE /api/channels/{channelId}
Remove a channel registration.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
channelId | string | Internal channel ID |
Response
{ "success": true, "message": "Channel deleted successfully" }| Status Code | Meaning |
|---|---|
| 200 | Deleted |
| 404 | Channel not found |
POST /api/channels/refresh-names
Refresh all channel names by querying the Discord API. Channels that no longer exist on Discord are automatically removed from the database.
Response
{
"success": true,
"updated_count": 3,
"removed_count": 1,
"total_channels": 4,
"errors": ["Failed to refresh channel 987654321098765432"]
}Notes
errorsis only present when some channels failed to refresh.- Channels deleted from Discord are removed from the local database.
| Status Code | Meaning |
|---|---|
| 200 | Refresh complete |
| 500 | Internal error |
Settings
GET /api/settings
Get all application settings in a single response.
Response
{
"discord": {
"application_id": "123456789012345678",
"bot_token": "••••••••",
"guild_id": "123456789012345678",
"announcement_role_id": "123456789012345678",
"mention_everyone": false,
"event_duration_minutes": 60,
"match_reminder_minutes": 15,
"player_reminder_minutes": 5,
"announcer_voice": "default",
"voice_announcements_enabled": true
},
"announcer": {
"announcer_voice": "default",
"voice_announcements_enabled": true
},
"ui": {
"auto_refresh_interval_seconds": 30
},
"scheduler": {
"match_check_cron": "0 * * * * *",
"cleanup_check_cron": "0 0 * * * *",
"channel_refresh_cron": "0 0 * * * *"
},
"voices": [
{
"id": "default",
"name": "Default",
"path": "/voices/default.mp3"
}
]
}Notes
bot_tokenis always masked in responses for security.
| Status Code | Meaning |
|---|---|
| 200 | Success |
GET /api/settings/discord
Get Discord bot settings only.
Response
Same as the "discord" object from GET /api/settings.
| Status Code | Meaning |
|---|---|
| 200 | Success |
PUT /api/settings/discord
Update Discord bot settings. Automatically restarts the Discord bot process after saving.
Request Body
{
"application_id": "123456789012345678",
"bot_token": "MTIzNDU2Nzg5...",
"guild_id": "123456789012345678",
"announcement_role_id": "123456789012345678",
"mention_everyone": false,
"event_duration_minutes": 60,
"match_reminder_minutes": 15,
"player_reminder_minutes": 5,
"announcer_voice": "default",
"voice_announcements_enabled": true,
"voice_channel_category_id": "123456789012345678",
"voice_channel_cleanup_delay_minutes": 5
}All fields are optional; omitted fields remain unchanged.
Notes
- Send the full unmasked token when updating
bot_token. Do not send the masked"••••••••"value. - After saving, the Discord bot process is restarted (via PM2 in dev, pkill in production Docker).
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Updated and bot restarted |
| 500 | Save failed |
GET /api/settings/announcer
Get voice announcer settings.
Response
{
"announcer_voice": "default",
"voice_announcements_enabled": true,
"match_start_delay_seconds": 5
}| Status Code | Meaning |
|---|---|
| 200 | Success |
PUT /api/settings/announcer
Update voice announcer settings.
Request Body
{
"announcer_voice": "default",
"voice_announcements_enabled": true,
"match_start_delay_seconds": 5
}| Field | Type | Validation |
|---|---|---|
announcer_voice | string | Valid voice ID |
voice_announcements_enabled | boolean | — |
match_start_delay_seconds | number | 0–300 inclusive |
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | match_start_delay_seconds out of range |
GET /api/settings/ui
Get UI display settings.
Response
{
"auto_refresh_interval_seconds": 30
}| Status Code | Meaning |
|---|---|
| 200 | Success |
PUT /api/settings/ui
Update UI display settings.
Request Body
{
"auto_refresh_interval_seconds": 30
}| Field | Type | Required | Validation |
|---|---|---|---|
auto_refresh_interval_seconds | number | Yes | 5–300 inclusive |
Response
{ "message": "UI settings updated successfully" }| Status Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Value out of range |
GET /api/settings/scheduler
Get cron job schedule settings.
Response
{
"match_check_cron": "0 * * * * *",
"cleanup_check_cron": "0 0 * * * *",
"channel_refresh_cron": "0 0 * * * *"
}| Status Code | Meaning |
|---|---|
| 200 | Success |
PUT /api/settings/scheduler
Update cron job schedules. Cron expressions must use the 6-part format (second minute hour day month weekday).
Request Body
{
"match_check_cron": "0 * * * * *",
"cleanup_check_cron": "0 0 * * * *",
"channel_refresh_cron": "0 0 * * * *"
}All fields are optional. Any provided expression must have exactly 6 space-separated parts.
Response
Returns the full updated scheduler settings object (same as GET response).
| Status Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Invalid cron expression (must have 6 parts) |
GET /api/settings/log-level
Get the current application log level.
Response
{
"log_level": "warning"
}Valid values: "debug", "info", "warning", "error", "critical".
| Status Code | Meaning |
|---|---|
| 200 | Success |
PUT /api/settings/log-level
Update the application log level. Takes effect within 5 seconds.
Request Body
{
"log_level": "info"
}| Field | Type | Required | Valid Values |
|---|---|---|---|
log_level | string | Yes | "debug", "info", "warning", "error", "critical" |
Response
{
"success": true,
"log_level": "info"
}| Status Code | Meaning |
|---|---|
| 200 | Updated |
| 400 | Invalid log level value |
Welcome Flow
GET /api/welcome-flow
Check whether the initial setup wizard has been completed.
Response
{
"isFirstRun": false,
"completed": true,
"dbReady": true,
"metadata": {
"screens_completed": [1, 2, 3],
"completion_date": "2024-01-01T00:00:00.000Z",
"setup_type": "standard"
}
}| Status Code | Meaning |
|---|---|
| 200 | Success |
PUT /api/welcome-flow
Mark the welcome wizard as completed.
Request Body
{
"setupType": "standard"
}| Field | Type | Required | Valid Values |
|---|---|---|---|
setupType | string | Yes | "pro_mode", "standard" |
Response
{ "success": true }Notes
- Sets a
welcome_flow_completedcookie valid for 30 days.
| Status Code | Meaning |
|---|---|
| 200 | Completed |
| 400 | Missing or invalid setupType |
PUT /api/welcome-flow/screen
Record which screen the user has reached in the welcome wizard.
Request Body
{
"screen": 2
}| Field | Type | Required | Description |
|---|---|---|---|
screen | number | Yes | Current screen number |
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Saved |
Upload
POST /api/upload/event-image
Upload an image for a match or tournament event. Accepts multipart/form-data.
Request
Form field image containing the image file.
| Constraint | Value |
|---|---|
| Allowed types | JPEG, PNG, WebP, GIF |
| Max file size | 5 MB |
File signatures are verified server-side (not just MIME type).
Response
{
"success": true,
"imageUrl": "/uploads/events/abc123.jpg",
"filename": "abc123.jpg"
}| Status Code | Meaning |
|---|---|
| 200 | Uploaded |
| 400 | Missing file, invalid type, or file too large |
DELETE /api/upload/event-image
Delete a previously uploaded event image.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
imageUrl | string | Yes | Full relative URL (e.g., /uploads/events/abc123.jpg) |
Notes
- The
imageUrlmust start with/uploads/events/. Requests targeting other paths are rejected.
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Deleted |
| 400 | Missing or invalid imageUrl |
Matches
Match status follows this progression:
created → gather → assign → battle → complete
↘ cancelledGET /api/matches
List matches. Returns active matches by default.
Query Parameters
| Parameter | Value | Description |
|---|---|---|
status | "complete" | Return completed matches instead of active ones |
Response
[
{
"id": "1",
"name": "Grand Finals",
"description": "Best of 5",
"status": "battle",
"game_id": "overwatch-2",
"game_name": "Overwatch 2",
"game_icon": "/games/overwatch-2/icon.png",
"game_color": "#F99E1A",
"map_codes_supported": true,
"maps": ["king-s-row", "hanamura"],
"map_codes": { "king-s-row": "ABCD1234" },
"map_id": "competitive",
"map_name": "Competitive",
"start_time": "2024-12-31T20:00:00.000Z",
"start_date": "2024-12-31T20:00:00.000Z",
"rules": "competitive",
"rounds": 5,
"livestream_link": "https://twitch.tv/example",
"blue_team_voice_channel": "123456789012345678",
"red_team_voice_channel": "123456789012345679",
"tournament_name": "Winter Cup",
"tournament_round": 3,
"tournament_bracket_type": "winners",
"participant_count": 10,
"created_at": "2024-12-01T00:00:00.000Z",
"updated_at": "2024-12-31T20:00:00.000Z"
}
]| Status Code | Meaning |
|---|---|
| 200 | Success |
POST /api/matches
Create a new match.
Request Body
{
"name": "Grand Finals",
"description": "Best of 5",
"gameId": "overwatch-2",
"gameModeId": "competitive",
"mapId": "king-s-row",
"maps": ["king-s-row", "hanamura", "blizzard-world"],
"rules": "competitive",
"rounds": 5,
"startDate": "2024-12-31T20:00:00.000Z",
"livestreamLink": "https://twitch.tv/example",
"announcements": [
{ "minutesBefore": 60, "message": "Match starts in 1 hour!" },
{ "minutesBefore": 5, "message": "Match starting soon!" }
],
"playerNotifications": true,
"eventImageUrl": "/uploads/events/abc123.jpg"
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Match display name |
gameId | string | Yes | Game identifier |
gameModeId | string | Yes | Game mode identifier |
description | string | No | Match description |
mapId | string | No | Single map identifier |
maps | string[] | No | Ordered list of map IDs for multi-map matches |
rules | "casual" | "competitive" | No | Ruleset |
rounds | number | No | Number of rounds/maps |
startDate | ISO8601 | No | Scheduled start time |
livestreamLink | string | No | Livestream URL |
announcements | object[] | No | Scheduled announcement messages |
playerNotifications | boolean | No | Whether to send reminders to participants |
eventImageUrl | string | No | URL from upload endpoint |
Response
Created match object (201 status, same shape as list item).
| Status Code | Meaning |
|---|---|
| 201 | Created |
| 400 | Validation error |
GET /api/matches/{matchId}
Get a single match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
Single match object. Includes tournament_allow_match_editing boolean when the match is part of a tournament.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
PUT /api/matches/{matchId}
Update a match. Only allowed before the match reaches "battle" status. Tournament matches may additionally be restricted by allow_match_editing.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Request Body
{
"name": "Updated Name",
"description": "Updated description",
"startDate": "2025-01-01T20:00:00.000Z",
"rules": "casual",
"rounds": 3,
"livestreamLink": "https://twitch.tv/updated",
"maps": ["hanamura", "ilios"]
}All fields are optional.
Response
Updated match object.
| Status Code | Meaning |
|---|---|
| 200 | Updated |
| 403 | Match is in battle/complete/cancelled status, or tournament disallows editing |
| 404 | Match not found |
DELETE /api/matches/{matchId}
Delete a match and clean up associated resources.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Notes
- Deletes the event image if one was uploaded.
- Queues Discord message deletions for any announcements that were sent.
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Deleted |
| 404 | Match not found |
GET /api/matches/{matchId}/participants
List all participants signed up for a match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Notes
- Participants are added and removed via Discord bot commands, not via REST API.
Response
{
"participants": [
{
"id": "1",
"user_id": "user-abc",
"discord_user_id": "123456789012345678",
"username": "Player1",
"avatar_url": "https://cdn.discordapp.com/avatars/...",
"joined_at": "2024-12-31T18:00:00.000Z",
"signup_data": {},
"team_assignment": "blue",
"receives_map_codes": true
}
],
"signupConfig": {}
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
GET /api/matches/{matchId}/games
Get the individual games (rounds) within a match. Auto-initializes game entries if none exist.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
{
"success": true,
"games": [
{
"id": "1",
"match_id": "1",
"round": 1,
"participant1_id": "1",
"participant2_id": "2",
"map_id": "king-s-row",
"winner_id": null,
"status": "pending",
"created_at": "2024-12-31T20:00:00.000Z"
}
]
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
GET /api/matches/{matchId}/games-with-results
Get match games along with full result details (scores, winner, etc.).
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
Array of game objects with result data merged in.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
POST /api/matches/{matchId}/assign-teams
Assign participants to Blue, Red, or Reserve teams.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Request Body
{
"teamAssignments": [
{
"participantId": "1",
"team": "blue",
"receives_map_codes": true
},
{
"participantId": "2",
"team": "red",
"receives_map_codes": false
}
],
"blueTeamVoiceChannel": "123456789012345678",
"redTeamVoiceChannel": "123456789012345679"
}| Field | Type | Required | Description |
|---|---|---|---|
teamAssignments | object[] | Yes | List of participant → team assignments |
teamAssignments[].participantId | string | Yes | Participant ID |
teamAssignments[].team | "blue" | "red" | "reserve" | Yes | Team assignment |
teamAssignments[].receives_map_codes | boolean | No | Whether this player receives map codes |
blueTeamVoiceChannel | string | No | Discord voice channel ID for Blue team |
redTeamVoiceChannel | string | No | Discord voice channel ID for Red team |
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Assigned |
| 404 | Match not found |
GET /api/matches/{matchId}/overall-score
Get the aggregate score for a match (Blue vs Red across all games).
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
Scoring object with aggregate results for both teams.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
POST /api/matches/{matchId}/transition
Transition a match to a new status.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Request Body
{
"newStatus": "gather"
}| Field | Type | Required | Valid Values |
|---|---|---|---|
newStatus | string | Yes | "created", "gather", "assign", "battle", "complete", "cancelled" |
Response
Updated match object.
| Status Code | Meaning |
|---|---|
| 200 | Transitioned |
| 400 | Invalid transition or status |
| 404 | Match not found |
GET /api/matches/{matchId}/reminders
List all scheduled announcements and reminders for a match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
{
"match": {
"id": "1",
"name": "Grand Finals",
"start_date": "2024-12-31T20:00:00.000Z",
"player_notifications": true,
"status": "gather"
},
"reminders": [
{
"reminder_time": "2024-12-31T19:00:00.000Z",
"message": "Match starts in 1 hour!",
"status": "pending"
}
],
"reminderCount": 1
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
POST /api/matches/{matchId}/map-codes
Save map codes for the match’s maps. Codes are shared with players who have receives_map_codes = true.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Request Body
{
"mapCodes": {
"king-s-row": "ABCD1234",
"hanamura": "EFGH5678"
}
}Map code values are capped at 24 characters each.
Response
{
"success": true,
"mapCodes": {
"king-s-row": "ABCD1234",
"hanamura": "EFGH5678"
}
}| Status Code | Meaning |
|---|---|
| 200 | Saved |
| 400 | Code exceeds 24 characters |
| 404 | Match not found |
GET /api/matches/{matchId}/map-codes
Retrieve saved map codes for a match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
{
"mapCodes": {
"king-s-row": "ABCD1234"
}
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
POST /api/matches/{matchId}/map-notes
Save a note for a specific map within the match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Request Body
{
"mapId": "king-s-row",
"note": "Blue team focuses on point A"
}| Field | Type | Required |
|---|---|---|
mapId | string | Yes |
note | string | No |
Response
{
"success": true,
"note": "Blue team focuses on point A"
}| Status Code | Meaning |
|---|---|
| 200 | Saved |
| 404 | Match not found |
GET /api/matches/{matchId}/map-notes
Retrieve all map notes for a match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
Response
{
"notes": {
"king-s-row": "Blue team focuses on point A"
}
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match not found |
GET /api/matches/{matchId}/games/{gameId}/result
Get the recorded result of a single game within a match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
gameId | string | Game ID |
Response
Result object with scores and winner.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Match or game not found, or no result recorded yet |
POST /api/matches/{matchId}/games/{gameId}/result
Record the result (winner) for a single game within a match.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
matchId | string | Match ID |
gameId | string | Game ID |
Request Body
{
"gameId": "1",
"matchId": "1",
"winner": "team1"
}| Field | Type | Required | Description |
|---|---|---|---|
gameId | string | Yes | Must match URL parameter |
matchId | string | Yes | Must match URL parameter |
winner | "team1" | "team2" | Yes | Winning team |
Response
{
"success": true,
"message": "Blue Team wins!"
}| Status Code | Meaning |
|---|---|
| 200 | Result recorded |
| 400 | Validation error or mismatched IDs |
| 404 | Match or game not found |
Tournaments
Tournament status follows the same progression as matches:
created → gather → assign → battle → complete
↘ cancelledGET /api/tournaments
List tournaments. Returns active tournaments by default.
Query Parameters
| Parameter | Value | Description |
|---|---|---|
status | "complete" | Return completed tournaments instead of active ones |
Response
[
{
"id": "1",
"name": "Winter Cup",
"description": "Annual championship",
"status": "battle",
"game_id": "overwatch-2",
"game_name": "Overwatch 2",
"game_icon": "/games/overwatch-2/icon.png",
"game_color": "#F99E1A",
"game_mode_id": "competitive",
"format": "single-elimination",
"rounds_per_match": 3,
"ruleset": "competitive",
"max_participants": 64,
"allow_player_team_selection": false,
"allow_match_editing": true,
"event_image_url": "/uploads/events/abc123.jpg",
"start_date": "2025-01-01T00:00:00.000Z",
"start_time": "2025-01-01T18:00:00.000Z",
"participant_count": 48,
"has_bracket": true,
"created_at": "2024-12-01T00:00:00.000Z",
"updated_at": "2024-12-31T00:00:00.000Z"
}
]| Status Code | Meaning |
|---|---|
| 200 | Success |
POST /api/tournaments
Create a new tournament.
Request Body
{
"name": "Winter Cup",
"description": "Annual championship",
"gameId": "overwatch-2",
"gameModeId": "competitive",
"format": "single-elimination",
"startDate": "2025-01-01T00:00:00.000Z",
"startTime": "2025-01-01T18:00:00.000Z",
"roundsPerMatch": 3,
"ruleset": "competitive",
"maxParticipants": 64,
"eventImageUrl": "/uploads/events/abc123.jpg",
"allowPlayerTeamSelection": false,
"allowMatchEditing": true
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tournament display name |
gameId | string | Yes | Game identifier |
gameModeId | string | Yes | Game mode identifier |
format | "single-elimination" | "double-elimination" | Yes | Bracket format |
roundsPerMatch | number | Yes | Games per bracket match |
description | string | No | Tournament description |
startDate | ISO8601 | No | Start date |
startTime | ISO8601 | No | Start time |
ruleset | string | No | Rule set label |
maxParticipants | number | No | Participant cap |
eventImageUrl | string | No | URL from upload endpoint |
allowPlayerTeamSelection | boolean | No | Allow players to choose their team |
allowMatchEditing | boolean | No | Allow editing matches after creation |
Response
Created tournament object (201 status, same shape as list item).
| Status Code | Meaning |
|---|---|
| 201 | Created |
| 400 | Validation error |
GET /api/tournaments/{tournamentId}
Get full tournament details including teams and participants.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Response
Tournament object with an additional teams array:
{
"teams": [
{
"id": "1",
"tournament_id": "1",
"team_name": "Team Alpha",
"created_at": "2024-12-01T00:00:00.000Z",
"members": [
{
"id": "1",
"team_id": "1",
"user_id": "user-abc",
"discord_user_id": "123456789012345678",
"username": "Player1",
"avatar_url": "https://cdn.discordapp.com/avatars/...",
"joined_at": "2024-12-15T00:00:00.000Z"
}
]
}
]
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Tournament not found |
DELETE /api/tournaments/{tournamentId}
Delete a tournament and all associated data.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Notes
- Queues Discord message deletions for tournament and all associated matches.
- Deletes bracket matches, teams, and participants.
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Deleted |
| 404 | Tournament not found |
GET /api/tournaments/{tournamentId}/participants
Get all participants signed up for a tournament.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Response
{
"participants": [
{
"id": "1",
"user_id": "user-abc",
"username": "Player1",
"joined_at": "2024-12-15T00:00:00.000Z",
"team_assignment": "Team Alpha",
"signup_data": {}
}
],
"signupConfig": {}
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Tournament not found |
GET /api/tournaments/{tournamentId}/teams
Get all teams in a tournament with their members.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Response
Array of team objects (same shape as the teams array in the tournament detail response).
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Tournament not found |
POST /api/tournaments/{tournamentId}/teams
Create a new team in the tournament.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Request Body
{
"teamName": "Team Alpha"
}| Field | Type | Required |
|---|---|---|
teamName | string | Yes |
Response
Created team object with an empty members array (201 status).
| Status Code | Meaning |
|---|---|
| 201 | Created |
| 409 | Team name already exists in this tournament |
PUT /api/tournaments/{tournamentId}/teams
Assign participants to teams. Clears all previous team assignments before applying the new ones.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Request Body
{
"teams": [
{
"teamId": "1",
"members": [
{ "userId": "user-abc", "username": "Player1" },
{ "userId": "user-def", "username": "Player2" }
]
}
]
}Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Assigned |
| 404 | Tournament or team not found |
DELETE /api/tournaments/{tournamentId}/teams
Delete a specific team from the tournament.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
teamId | string | Yes | Team ID to delete |
Response
{ "success": true }| Status Code | Meaning |
|---|---|
| 200 | Deleted |
| 400 | Missing teamId |
| 404 | Team not found |
GET /api/tournaments/{tournamentId}/matches
Get all bracket matches for a tournament.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Response
{
"matches": [
{
"id": "1",
"round": 1,
"bracket_type": "winners",
"team1": { "id": "1", "name": "Team Alpha" },
"team2": { "id": "2", "name": "Team Beta" },
"winner": null,
"status": "ongoing",
"rawStatus": "battle",
"match_order": 1
}
],
"count": 4
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Tournament not found |
GET /api/tournaments/{tournamentId}/standings
Get win/loss standings for all teams in the tournament.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Response
{
"standings": [
{
"team_id": "1",
"team_name": "Team Alpha",
"matches_played": 3,
"wins": 3,
"losses": 0
}
]
}| Status Code | Meaning |
|---|---|
| 200 | Success |
| 404 | Tournament not found |
POST /api/tournaments/{tournamentId}/transition
Transition a tournament to a new status.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Request Body
{
"newStatus": "battle"
}| Field | Type | Required | Valid Values |
|---|---|---|---|
newStatus | string | Yes | "created", "gather", "assign", "battle", "complete", "cancelled" |
Notes
- Transitioning to
"battle"requires at least 2 teams with members assigned.
Response
Updated tournament object.
| Status Code | Meaning |
|---|---|
| 200 | Transitioned |
| 400 | Invalid transition, or requirements not met (e.g., insufficient teams) |
| 404 | Tournament not found |
POST /api/tournaments/{tournamentId}/bracket-assignments
Save the seeding positions for each team before generating bracket matches.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Request Body
{
"assignments": [
{ "position": 1, "teamId": "1" },
{ "position": 2, "teamId": "2" }
]
}Notes
- Tournament must be in
"assign"status.
Response
{
"message": "Bracket assignments saved successfully",
"assignments": [...]
}| Status Code | Meaning |
|---|---|
| 200 | Saved |
| 400 | Tournament not in assign status |
| 404 | Tournament not found |
POST /api/tournaments/{tournamentId}/generate-matches
Generate bracket matches for the tournament. If bracket assignments are provided, they are applied first; otherwise teams are seeded automatically.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Request Body (optional)
{
"assignments": [
{ "position": 1, "teamId": "1" },
{ "position": 2, "teamId": "2" }
]
}Notes
- Tournament must be in
"assign"status. - Returns an error if bracket matches have already been generated.
Response
{
"message": "Bracket generated successfully",
"matchCount": 8,
"format": "single-elimination",
"tournamentId": "1"
}| Status Code | Meaning |
|---|---|
| 200 | Bracket generated |
| 400 | Not in assign status or matches already generated |
| 404 | Tournament not found |
POST /api/tournaments/{tournamentId}/progress
Advance the tournament bracket to the next round, or finalize the tournament if all rounds are complete.
URL Parameters
| Parameter | Type | Description |
|---|---|---|
tournamentId | string | Tournament ID |
Notes
- Tournament must be in
"battle"status. - All matches in the current round must be completed before progressing.
- Handles both single-elimination and double-elimination bracket logic.
Response (new round generated)
{
"message": "Next round generated",
"matchCount": 4,
"nextRound": 2,
"tournamentId": "1"
}Response (tournament complete)
{
"message": "Tournament complete",
"tournamentId": "1",
"winner": "Team Alpha"
}| Status Code | Meaning |
|---|---|
| 200 | Progressed or tournament finalized |
| 400 | Not in battle status or current round not fully complete |
| 404 | Tournament not found |