REST API Reference

Base URL: /api/v1  •  All responses are application/json. Endpoints marked Admin require an active admin session cookie.

Players

GET /api/v1/players

Returns all registered players.

Query Parameters
ParameterTypeDescription
displaynamestringOptional. 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"
  }
]
GET /api/v1/players/{steamID}

Returns a single player by their 64-bit Steam ID.

Path Parameters
ParameterTypeDescription
steamIDstring64-bit Steam ID
Responses
StatusMeaning
200Player object
404Player not found
GET /api/v1/players/by-discord/{discordUsername}

Returns a single player by linked Discord username (case-insensitive).

Path Parameters
ParameterTypeDescription
discordUsernamestringLinked Discord username. A leading @ is accepted.
Responses
StatusMeaning
200Player object
400Missing or empty discordUsername
404Player not found
PUT /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
StatusMeaning
200Updated player object
403Forbidden (admin required)
404Player not found

Seasons

GET /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"
  }
]
GET /api/v1/seasons/{id}

Returns a single season by ID, including season-scoped groups and assigned teams for historical standings.

Path Parameters
ParameterTypeDescription
idintegerSeason 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
StatusMeaning
200Season detail object with groups and ungrouped teams
404Season not found
GET /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
ParameterTypeDescription
idintegerSeason 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
StatusMeaning
200Season-registered team list
404Season not found
GET /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
ParameterTypeDescription
statusstringOptional. Filter by scheduled, pending, unscheduled, or a comma-separated combination like scheduled,pending.
completed_onlybooleanIf true, return only completed series.
exclude_completedbooleanIf 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
StatusMeaning
200Season schedule list
400Conflicting filters or invalid status filter
404Season not found
GET /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
ParameterTypeDescription
discordusernamestringOptional. 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_hoursintegerOptional. 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
StatusMeaning
200Next future scheduled series
404No upcoming scheduled series, season not found, player not found for Discord username, or player is not on a team
GET /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
ParameterTypeDescription
discordusernamestringRequired. 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
StatusMeaning
200Grouped season series for the player's team
400discordusername missing
404Season not found, player not found for Discord username, player is not on a team, or team is not registered for that season
GET /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
ParameterTypeDescription
idintegerSeason ID.
seriesIDintegerSeries 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
StatusMeaning
200Series participant list (possibly empty)
400Invalid seriesID
404Season or series not found
GET /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
StatusMeaning
200Group standings for the season
404Season not found
GET /api/v1/seasons/{id}/series/{seriesID}/availability-suggestions

Returns overlap start-time suggestions from both teams' saved availability windows.

Path Parameters
ParameterTypeDescription
idintegerSeason ID (or active shortcut where supported by route).
seriesIDintegerSeries ID within the season.
Query Parameters
ParameterTypeDescription
limitintegerOptional. Max suggestions to return. Range: 1-25. Default 5.
step_minutesintegerOptional. Search interval minutes. Range: 5-180. Default 30.
afterRFC3339 timestampOptional lower bound for suggestion start times. Default is now.
include_window_detailsbooleanOptional. 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
StatusMeaning
200Availability suggestions response (possibly empty suggestions list)
400Invalid seriesID, limit, step_minutes, after, or invalid season group-stage window
404Season or series not found
GET /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
ParameterTypeDescription
game_numberintegerOptional 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
StatusMeaning
200Readiness payload with can_create and reason info
400Invalid season ID, series ID, or game_number
404Season or series not found
GET /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
StatusMeaning
200Unified standings and playoff summary for the season
404Season not found
GET /api/v1/seasons/{id}/players

Returns all players on teams registered for the season, using the same player shape as /api/v1/players.

Responses
StatusMeaning
200Season player list
404Season not found
POST /api/v1/seasons Admin

Creates a new season in draft status.

Request Body
{
  "name": "Season 2",          // required
  "description": "Optional."   // optional
}
Responses
StatusMeaning
201Created season object
400Validation error (name required)
403Forbidden (admin required)
PUT /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
StatusMeaning
200Updated season object
403Forbidden (admin required)
404Season not found
DELETE /api/v1/seasons/{id} Admin

Permanently removes a season by ID.

Responses
StatusMeaning
200{"message":"season deleted"}
403Forbidden (admin required)
404Season not found

Teams

GET /api/v1/teams

Returns all teams ordered by creation date descending, including roster totals, pending join requests, and smurf-check counts.

Query Parameters
ParameterTypeDescription
namestringOptional. 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"
  }
]
GET /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
ParameterTypeDescription
idintegerTeam 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
StatusMeaning
200Single team object
404Team not found
POST /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)
FieldTypeDescription
namestringTeam name, max 25 characters (required)
logofileImage file — PNG, JPG, or GIF (required)
Responses
StatusMeaning
201Created team object
400Validation error (bad name, missing logo)
401Authentication required
409Team name taken or player already on a team
503S3 uploader not configured

Error Format

All error responses share a consistent JSON envelope:

{
  "error": "human-readable description"
}