REST API Reference
Base URL: /api/v1 • All responses are application/json. Endpoints marked Admin require an active admin session cookie.
Players
/api/v1/players
Returns all registered players.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
displayname | string | Optional. Performs an exact case-insensitive display-name lookup and returns a single player object instead of the full list. |
Response 200
[
{
"id": 1,
"steam_id": "76561198000000000",
"display_name": "PlayerOne",
"avatar_url": "https://...",
"country_code": "US",
"is_banned": false,
"is_admin": false,
"smurf_status": "unchecked",
"rank_tier": 43,
"rank_tier_label": "Archon 3",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
]
/api/v1/players/{steamID}
Returns a single player by their 64-bit Steam ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
steamID | string | 64-bit Steam ID |
Responses
| Status | Meaning |
|---|---|
| 200 | Player object |
| 404 | Player not found |
/api/v1/players/by-discord/{discordUsername}
Returns a single player by linked Discord username (case-insensitive).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
discordUsername | string | Linked Discord username. A leading @ is accepted. |
Responses
| Status | Meaning |
|---|---|
| 200 | Player object |
| 400 | Missing or empty discordUsername |
| 404 | Player not found |
/api/v1/players/{steamID}
Admin
Partially update a player's admin-writable fields. Send only the fields you want to change.
Request Body
{
"is_banned": true, // optional boolean
"is_admin": false, // optional boolean
"smurf_status": "approved", // optional: unchecked | approved | failed
"rank_tier": 43 // optional: 10-89 (e.g. 43 = Archon 3)
}
Responses
| Status | Meaning |
|---|---|
| 200 | Updated player object |
| 403 | Forbidden (admin required) |
| 404 | Player not found |
Seasons
/api/v1/seasons
Returns all seasons ordered by creation date descending.
Response 200
[
{
"id": 1,
"name": "Season 1",
"description": "The inaugural season.",
"status": "active",
"start_date": "2026-01-01",
"end_date": null,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
]
/api/v1/seasons/{id}
Returns a single season by ID, including season-scoped groups and assigned teams for historical standings.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | integer | Season ID |
Response 200
{
"id": 4,
"name": "Season 4",
"description": "Spring Split",
"status": "completed",
"start_date": "2026-02-01",
"end_date": "2026-04-10",
"created_at": "2026-01-20T00:00:00Z",
"updated_at": "2026-04-10T00:00:00Z",
"groups": [
{
"id": 12,
"name": "Group A",
"teams": [
{
"team_id": 101,
"team_name": "Alpha Squad",
"registered_at": "2026-01-25T13:00:00Z"
}
]
}
],
"ungrouped": [
{
"team_id": 202,
"team_name": "Late Entry",
"registered_at": "2026-01-28T17:15:00Z"
}
]
}
Responses
| Status | Meaning |
|---|---|
| 200 | Season detail object with groups and ungrouped teams |
| 404 | Season not found |
/api/v1/seasons/{id}/teams
Returns only the teams registered for the season, using the same roster aggregates as /api/v1/teams plus season-specific registration, group, and standing fields.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | integer | Season ID |
Response 200
[
{
"id": 101,
"name": "Alpha Squad",
"logo_urls": {
"thumb": "https://...",
"square": "https://..."
},
"captain_player_id": 42,
"player_count": 5,
"pending_join_request_count": 1,
"smurf_status_counts": {
"unchecked": 0,
"approved": 5,
"failed": 0
},
"registered_at": "2026-01-25T13:00:00Z",
"group_id": 12,
"group_name": "Group A",
"played_matches": 4,
"wins": 3,
"losses": 1,
"points": 3,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-20T00:00:00Z"
}
]
Responses
| Status | Meaning |
|---|---|
| 200 | Season-registered team list |
| 404 | Season not found |
/api/v1/seasons/{id}/schedule
Returns all series for the season, including completed series by default. You can also use the active-season shortcut at /api/v1/seasons/active/schedule.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Optional. Filter by scheduled, pending, unscheduled, or a comma-separated combination like scheduled,pending. |
completed_only | boolean | If true, return only completed series. |
exclude_completed | boolean | If true, omit completed series. |
Response 200
[
{
"id": 55,
"season_id": 4,
"group_id": 12,
"group_name": "Group A",
"team_a_id": 101,
"team_a_name": "Alpha Squad",
"team_b_id": 202,
"team_b_name": "Late Entry",
"num_matches": 2,
"schedule_status": "scheduled",
"scheduled_start_at": "2026-02-15T20:00:00Z",
"approved_match_count": 2,
"pending_match_count": 0,
"rejected_match_count": 0,
"completed": true,
"created_at": "2026-02-01T00:00:00Z"
}
]
Responses
| Status | Meaning |
|---|---|
| 200 | Season schedule list |
| 400 | Conflicting filters or invalid status filter |
| 404 | Season not found |
/api/v1/seasons/{id}/nextseries
Returns the next scheduled series for the season whose confirmed start time is still in the future. You can also use /api/v1/seasons/active/nextseries.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
discordusername | string | Optional. Case-insensitive lookup of a linked Discord username. If supplied, returns the next scheduled series for that player's current team in the season. |
rewind_hours | integer | Optional. Search backward this many hours from now when looking for the next scheduled series. Default is 0. |
Response 200
{
"id": 46,
"season_id": 1,
"group_id": 0,
"group_name": "Lower Round 1",
"team_a_id": 10,
"team_a_name": "Aegis 898",
"team_b_id": 12,
"team_b_name": "Kestrel 966",
"num_matches": 3,
"schedule_status": "scheduled",
"scheduled_start_at": "2026-04-04T00:30:00Z",
"approved_match_count": 0,
"pending_match_count": 0,
"rejected_match_count": 0,
"completed": false,
"created_at": "2026-04-02T19:17:43Z"
}
Responses
| Status | Meaning |
|---|---|
| 200 | Next future scheduled series |
| 404 | No upcoming scheduled series, season not found, player not found for Discord username, or player is not on a team |
/api/v1/seasons/{id}/myseries
Returns the requested player's team series for the season, grouped into bot-friendly buckets. You can also use /api/v1/seasons/active/myseries.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
discordusername | string | Required. Case-insensitive lookup of a linked Discord username. |
Response 200
{
"season_id": 1,
"player_id": 155,
"player_display_name": "RxDealer",
"discord_username": "rxdealer",
"team_id": 10,
"team_name": "Aegis 898",
"needs_response": [],
"scheduled": [
{
"season_id": 1,
"series_id": 46,
"group_name": "Lower Round 1",
"opponent_team_id": 12,
"opponent_team_name": "Kestrel 966",
"schedule_status": "scheduled",
"scheduled_start_at": "2026-04-04T00:30:00Z",
"needs_response": false,
"num_matches": 3,
"approved_match_count": 0,
"pending_match_count": 0,
"rejected_match_count": 0,
"completed": false
}
],
"pending": [],
"unscheduled": [],
"completed": []
}
Responses
| Status | Meaning |
|---|---|
| 200 | Grouped season series for the player's team |
| 400 | discordusername missing |
| 404 | Season not found, player not found for Discord username, player is not on a team, or team is not registered for that season |
/api/v1/seasons/{id}/series/{seriesID}/participants
Returns players from both teams plus active casters for the series who have linked Discord usernames.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | integer | Season ID. |
seriesID | integer | Series ID within the season. |
Response 200
{
"season_id": 1,
"series_id": 46,
"linked_only": true,
"participants": [
{
"player_id": 155,
"display_name": "RxDealer",
"discord_username": "rxdealer",
"timezone": "America/Chicago",
"scheduled_start_at": "2026-04-04T00:30:00Z"
}
]
}
Responses
| Status | Meaning |
|---|---|
| 200 | Series participant list (possibly empty) |
| 400 | Invalid seriesID |
| 404 | Season or series not found |
/api/v1/seasons/{id}/standings
Returns group-stage standings grouped by season group, using approved group-stage match results only. You can also use /api/v1/seasons/active/standings.
Response 200
[
{
"group_id": 12,
"group_name": "Group A",
"standings": [
{
"team_id": 101,
"team_name": "Alpha Squad",
"scheduled_series": 3,
"completed_series": 2,
"remaining_series": 1,
"played_matches": 4,
"wins": 3,
"losses": 1,
"points": 3
}
]
}
]
Responses
| Status | Meaning |
|---|---|
| 200 | Group standings for the season |
| 404 | Season not found |
/api/v1/seasons/{id}/series/{seriesID}/availability-suggestions
Returns overlap start-time suggestions from both teams' saved availability windows.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | integer | Season ID (or active shortcut where supported by route). |
seriesID | integer | Series ID within the season. |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Optional. Max suggestions to return. Range: 1-25. Default 5. |
step_minutes | integer | Optional. Search interval minutes. Range: 5-180. Default 30. |
after | RFC3339 timestamp | Optional lower bound for suggestion start times. Default is now. |
include_window_details | boolean | Optional. If true, includes matching window objects with readable display_lines. |
Response 200
{
"season_id": 1,
"series_id": 46,
"team_a_id": 10,
"team_a_name": "Aegis 898",
"team_b_id": 12,
"team_b_name": "Kestrel 966",
"after": "2026-04-04T00:00:00Z",
"window_start": "2026-04-04T00:00:00Z",
"window_end": "2026-04-20T00:00:00Z",
"step_minutes": 30,
"suggestions": [
{
"start_at": "2026-04-05T01:30:00Z",
"team_a_matching_window_ids": [101],
"team_b_matching_window_ids": [205],
"team_a_matching_windows": [
{
"id": 101,
"team_id": 10,
"weekdays_mask": 62,
"start_minute": 1020,
"end_minute": 1320,
"timezone": "America/Chicago",
"display_lines": ["Mon, Tue, Wed, Thu, Fri 5:00 PM to 10:00 PM"]
}
],
"team_b_matching_windows": [
{
"id": 205,
"team_id": 12,
"weekdays_mask": 126,
"start_minute": 1080,
"end_minute": 1380,
"timezone": "America/New_York",
"display_lines": ["Sun, Mon, Tue, Wed, Thu, Fri 6:00 PM to 11:00 PM"]
}
]
}
]
}
Responses
| Status | Meaning |
|---|---|
| 200 | Availability suggestions response (possibly empty suggestions list) |
| 400 | Invalid seriesID, limit, step_minutes, after, or invalid season group-stage window |
| 404 | Season or series not found |
/api/v1/seasons/{id}/series/{seriesID}/lobby/readiness
Preflight check that reports whether lobby creation is currently allowed for a game, plus a machine-friendly reason code.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
game_number | integer | Optional target game number. Default 1. |
Response 200
{
"season_id": 1,
"series_id": 46,
"game_number": 1,
"can_create": false,
"reason_code": "too_early",
"reason": "lobby creation opens 60 minutes before start time",
"schedule_status": "scheduled",
"scheduled_start_at": "2026-04-04T00:30:00Z",
"lobby_exists": false,
"bot_configured": true,
"opens_at": "2026-04-03T23:30:00Z"
}
Responses
| Status | Meaning |
|---|---|
| 200 | Readiness payload with can_create and reason info |
| 400 | Invalid season ID, series ID, or game_number |
| 404 | Season or series not found |
/api/v1/seasons/{id}/standings/summary
Returns unified standings data for bots, including group-stage standings and playoff match state when playoffs are active. You can also use /api/v1/seasons/active/standings/summary.
Response 200
{
"season_id": 1,
"phase": "playoffs",
"playoff_bracket_generated": true,
"group_standings": [
{
"group_id": 12,
"group_name": "Group A",
"standings": [
{
"team_id": 101,
"team_name": "Alpha Squad",
"scheduled_series": 3,
"completed_series": 2,
"remaining_series": 1,
"played_matches": 4,
"wins": 3,
"losses": 1,
"points": 3
}
]
}
],
"playoff_matches": [
{
"id": 9,
"season_id": 1,
"match_code": "LF",
"bracket_kind": "lower",
"round_number": 2,
"slot_number": 1,
"round_name": "Lower Final",
"best_of": 3,
"team_a_name": "Alpha Squad",
"team_b_name": "Bravo Team",
"schedule_status": "scheduled",
"scheduled_start_at": "2026-04-04T00:30:00Z",
"approved_match_count": 0,
"team_a_wins": 0,
"team_b_wins": 0,
"completed": false
}
]
}
Responses
| Status | Meaning |
|---|---|
| 200 | Unified standings and playoff summary for the season |
| 404 | Season not found |
/api/v1/seasons/{id}/players
Returns all players on teams registered for the season, using the same player shape as /api/v1/players.
Responses
| Status | Meaning |
|---|---|
| 200 | Season player list |
| 404 | Season not found |
/api/v1/seasons
Admin
Creates a new season in draft status.
Request Body
{
"name": "Season 2", // required
"description": "Optional." // optional
}
Responses
| Status | Meaning |
|---|---|
| 201 | Created season object |
| 400 | Validation error (name required) |
| 403 | Forbidden (admin required) |
/api/v1/seasons/{id}
Admin
Partially updates a season. Setting status to active automatically demotes any other active season to draft.
Request Body
{
"name": "Season 1 – Updated", // optional
"description": "New desc.", // optional
"status": "active" // optional: draft | active | completed
}
Responses
| Status | Meaning |
|---|---|
| 200 | Updated season object |
| 403 | Forbidden (admin required) |
| 404 | Season not found |
/api/v1/seasons/{id}
Admin
Permanently removes a season by ID.
Responses
| Status | Meaning |
|---|---|
| 200 | {"message":"season deleted"} |
| 403 | Forbidden (admin required) |
| 404 | Season not found |
Teams
/api/v1/teams
Returns all teams ordered by creation date descending, including roster totals, pending join requests, and smurf-check counts.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | Optional. Performs a case-insensitive partial-name search and returns all matching team objects. |
Response 200
[
{
"id": 1,
"name": "Alpha Squad",
"logo_urls": {
"thumb": "https://...",
"square": "https://...",
"medium": "https://...",
"large": "https://..."
},
"captain_player_id": 42,
"player_count": 5,
"pending_join_request_count": 2,
"smurf_status_counts": {
"unchecked": 1,
"approved": 3,
"failed": 1
},
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
]
/api/v1/teams/{id}
Returns a single team with the same payload shape used by the team-list endpoint and the name query lookup.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | integer | Team ID |
Response 200
{
"id": 1,
"name": "Alpha Squad",
"logo_urls": {
"thumb": "https://...",
"square": "https://...",
"medium": "https://...",
"large": "https://..."
},
"captain_player_id": 42,
"player_count": 5,
"pending_join_request_count": 2,
"smurf_status_counts": {
"unchecked": 1,
"approved": 3,
"failed": 1
},
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
Responses
| Status | Meaning |
|---|---|
| 200 | Single team object |
| 404 | Team not found |
/api/v1/teams
Auth required
Creates a new team. The authenticated player becomes the captain. Requires an active session cookie. Submitted as multipart/form-data.
Request Body (multipart/form-data)
| Field | Type | Description |
|---|---|---|
name | string | Team name, max 25 characters (required) |
logo | file | Image file — PNG, JPG, or GIF (required) |
Responses
| Status | Meaning |
|---|---|
| 201 | Created team object |
| 400 | Validation error (bad name, missing logo) |
| 401 | Authentication required |
| 409 | Team name taken or player already on a team |
| 503 | S3 uploader not configured |
Error Format
All error responses share a consistent JSON envelope:
{
"error": "human-readable description"
}