{
  "openapi": "3.0.3",
  "info": {
    "title": "TopDeck.gg API",
    "description": "Access tournament data from TopDeck.gg — players, standings, decklists, matchups, and more. Free to use with attribution required.\n\nAny project using this API must include a visible credit and link back to [TopDeck.gg](https://topdeck.gg).",
    "version": "2.0.0",
    "contact": {
      "name": "TopDeck.gg Support",
      "email": "contact@topdeck.gg",
      "url": "https://topdeck.gg"
    },
    "termsOfService": "https://topdeck.gg/api/docs"
  },
  "servers": [
    {
      "url": "https://topdeck.gg/api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/v2/tournaments": {
      "post": {
        "operationId": "searchTournaments",
        "summary": "Bulk Tournaments",
        "description": "Search completed tournaments by ID or filter (game, format, date range). Returns standings, decklists, and optional round data. Heavy endpoint — lower rate limit than default.",
        "tags": ["Tournaments"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkTournamentRequest"
              },
              "examples": {
                "queryMode": {
                  "summary": "Filter by game, format, and date range",
                  "value": {
                    "game": "Magic: The Gathering",
                    "format": "EDH",
                    "last": 30,
                    "columns": ["name", "decklist", "wins", "draws", "losses"],
                    "rounds": true
                  }
                },
                "tidMode": {
                  "summary": "Fetch specific tournaments by ID",
                  "value": {
                    "TID": ["abc123", "def456"],
                    "columns": ["name", "decklist", "wins", "losses"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Array of tournament objects with standings and optional round data.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BulkTournament"
                  }
                },
                "example": [
                  {
                    "TID": "abc123",
                    "tournamentName": "Weekly EDH",
                    "swissNum": 5,
                    "startDate": 1627844461,
                    "game": "Magic: The Gathering",
                    "format": "EDH",
                    "topCut": 8,
                    "eventData": {
                      "city": "New York",
                      "state": "NY",
                      "address": "Local Game Store"
                    },
                    "standings": [
                      {
                        "name": "Player Name",
                        "decklist": "~~Commanders~~\n...",
                        "wins": 4,
                        "draws": 1,
                        "losses": 0
                      }
                    ],
                    "rounds": [
                      {
                        "round": 1,
                        "tables": [
                          {
                            "table": 1,
                            "players": [
                              { "name": "Alice", "id": "abc" },
                              { "name": "Bob", "id": "def" }
                            ],
                            "winner": "Alice",
                            "winner_id": "abc",
                            "status": "Completed"
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}": {
      "get": {
        "operationId": "getTournament",
        "summary": "Single Tournament",
        "description": "Full tournament data: metadata, standings, and all rounds. Standings field set varies by tournament type — league tournaments report `successRate` instead of `winRate`. Decklists and Discord fields are returned only when the tournament has ended or the organizer enabled them.",
        "tags": ["Tournaments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          }
        ],
        "responses": {
          "200": {
            "description": "Full tournament object with info, standings, and rounds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FullTournament"
                },
                "example": {
                  "data": {
                    "tid": "abc123",
                    "name": "Tournament Name",
                    "game": "Magic: The Gathering",
                    "format": "EDH",
                    "startDate": 1627844461
                  },
                  "standings": [
                    {
                      "standing": 1,
                      "name": "Player Name",
                      "id": "abc123",
                      "points": 15,
                      "winRate": 0.8,
                      "opponentWinRate": 0.6
                    }
                  ],
                  "rounds": [
                    {
                      "round": 1,
                      "tables": []
                    }
                  ]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/info": {
      "get": {
        "operationId": "getTournamentInfo",
        "summary": "Tournament Info",
        "description": "Tournament metadata, schedule, and location.",
        "tags": ["Tournaments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          }
        ],
        "responses": {
          "200": {
            "description": "Tournament metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TournamentInfo"
                },
                "example": {
                  "tid": "abc123",
                  "name": "Tournament Name",
                  "game": "Magic: The Gathering",
                  "format": "EDH",
                  "startDate": 1627844461,
                  "endDate": 1627855261,
                  "status": "Complete",
                  "location": {
                    "name": "Card Kingdom Seattle",
                    "city": "Seattle",
                    "state": "WA",
                    "country": "US"
                  },
                  "headerImage": "https://..."
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/standings": {
      "get": {
        "operationId": "getTournamentStandings",
        "summary": "Standings",
        "description": "Standings for a tournament. League tournaments report `successRate`/`opponentSuccessRate` instead of `winRate`/`opponentWinRate`. Pairs-mode tournaments additionally include `gameWinRate` and `opponentGameWinRate`. Decklists and Discord fields are returned only when the tournament has ended or the organizer enabled them.",
        "tags": ["Tournaments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of player standings. Field set varies by tournament type — see description.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Standing"
                  }
                },
                "example": [
                  {
                    "standing": 1,
                    "name": "Player Name",
                    "id": "abc123",
                    "decklist": "~~Commanders~~\nAtraxa, Grand Unifier\n\n~~Mainboard~~\n...",
                    "deckObj": { "Commanders": { }, "Mainboard": { } },
                    "points": 15,
                    "winRate": 0.83,
                    "opponentWinRate": 0.7
                  }
                ]
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/players/{ID}": {
      "get": {
        "operationId": "getPlayer",
        "summary": "Player Details",
        "description": "Player details and match record. Decklist fields are returned only when the tournament has ended or the organizer enabled them.",
        "tags": ["Tournaments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          },
          {
            "name": "ID",
            "in": "path",
            "required": true,
            "description": "Player's unique identifier within the tournament.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Player details including standings, decklist, and match record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerDetail"
                },
                "example": {
                  "name": "Player Name",
                  "standing": 1,
                  "decklist": "~~Commanders~~\n...",
                  "deckObj": {
                    "Commanders": {},
                    "Mainboard": {}
                  },
                  "winRate": 0.83,
                  "gamesPlayed": 10,
                  "gamesWon": 8,
                  "byes": 0,
                  "gamesDrawn": 1,
                  "gamesLost": 1
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/rounds": {
      "get": {
        "operationId": "getTournamentRounds",
        "summary": "All Rounds",
        "description": "All rounds of a tournament, each with its tables and results. Decklists and Discord fields on seated players are returned only when the tournament has ended or the organizer enabled them.",
        "tags": ["Tournaments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of round objects, each containing tables with player matchups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Round"
                  }
                },
                "example": [
                  {
                    "round": 1,
                    "tables": [
                      {
                        "table": 1,
                        "players": [
                          { "name": "Player 1", "id": "abc" },
                          { "name": "Player 2", "id": "def" }
                        ],
                        "winner": "Player 1",
                        "winner_id": "abc",
                        "status": "Completed"
                      }
                    ]
                  },
                  {
                    "round": "Top 8",
                    "tables": []
                  }
                ]
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/rounds/latest": {
      "get": {
        "operationId": "getLatestRound",
        "summary": "Latest Round",
        "description": "Tables from the current/latest round. Returns a flat array of tables — no round wrapper, unlike `/rounds`. Decklists and Discord fields on seated players are returned only when the tournament has ended or the organizer enabled them.",
        "tags": ["Tournaments"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of table objects for the latest round.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Table"
                  }
                },
                "example": [
                  {
                    "table": 1,
                    "players": [
                      { "name": "Player 1", "id": "abc" },
                      { "name": "Player 2", "id": "def" }
                    ],
                    "winner": "Player 1",
                    "winner_id": "abc",
                    "status": "Completed"
                  },
                  {
                    "table": "Byes",
                    "players": [
                      { "name": "Player 5", "id": "ghi" }
                    ],
                    "status": "Bye"
                  }
                ]
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/attendees": {
      "get": {
        "operationId": "getTournamentAttendees",
        "summary": "Attendees",
        "description": "All tournament attendees: registered players, dropped players, and waitlisted users.\n\nRequires `judge` role or higher.",
        "tags": ["Staff"],
        "parameters": [
          {
            "$ref": "#/components/parameters/TID"
          }
        ],
        "responses": {
          "200": {
            "description": "Array of attendee objects including players, dropped players, and waitlisted users.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Attendee"
                  }
                },
                "example": [
                  {
                    "uid": "player123",
                    "name": "John Doe",
                    "email": "john@example.com",
                    "status": "player",
                    "standing": 1,
                    "decklist": "~~Commanders~~\n...",
                    "deckObj": { "Commanders": {}, "Mainboard": {} }
                  },
                  {
                    "uid": "dropped456",
                    "name": "Jane Smith",
                    "email": "jane@example.com",
                    "status": "dropped",
                    "standing": null
                  },
                  {
                    "uid": "waitlist789",
                    "name": "Bob Wilson",
                    "email": "bob@example.com",
                    "status": "waitlist",
                    "waitlistStatus": "waiting",
                    "waitlistPosition": 1,
                    "joinedAt": 1627840000
                  }
                ]
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "Insufficient permissions. Requires tournament staff access.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Forbidden: Insufficient permissions"
                }
              }
            }
          },
          "404": {
            "description": "Tournament not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Tournament not found" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/tournaments/{TID}/register": {
      "post": {
        "operationId": "registerPlayersByEmail",
        "summary": "Register Players by Email",
        "description": "Register one or more players by email. Requires `admin` role or higher.\n\nEmails matching an existing account are registered immediately. Emails without an account receive an invitation and convert on signup.\n\nIf the event has a player cap, requests that would exceed it return `409`. Pass `overrideCap: true` to bypass.",
        "tags": ["Staff"],
        "parameters": [
          { "$ref": "#/components/parameters/TID" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["emails"],
                "properties": {
                  "emails": {
                    "oneOf": [
                      { "type": "string", "format": "email" },
                      { "type": "array", "items": { "type": "string", "format": "email" }, "minItems": 1 }
                    ],
                    "description": "Single email or array of emails to register."
                  },
                  "overrideCap": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, skip the capacity check and register even if the tournament is at/over its player cap."
                  }
                }
              },
              "examples": {
                "single": {
                  "summary": "Register one email",
                  "value": { "emails": "player@example.com" }
                },
                "batch": {
                  "summary": "Register multiple emails",
                  "value": { "emails": ["a@example.com", "b@example.com"] }
                },
                "overrideCap": {
                  "summary": "Bypass capacity check",
                  "value": { "emails": ["vip@example.com"], "overrideCap": true }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-email outcomes. Partial success is represented in the response body — a 200 does not mean every email was added.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RegisterByEmailResult" },
                "example": {
                  "added": ["alice@example.com"],
                  "pending": ["newuser@example.com"],
                  "alreadyRegistered": ["bob@example.com"],
                  "failed": [],
                  "banned": [],
                  "capacity": 32,
                  "registeredCount": 24
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "Insufficient permissions. Requires tournament staff access (judge+).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Forbidden: Insufficient permissions" }
              }
            }
          },
          "404": {
            "description": "Tournament not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Tournament not found" }
              }
            }
          },
          "409": {
            "description": "Registering would exceed the event player cap. Pass `overrideCap: true` to bypass.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Event would exceed capacity (32/32 registered, 2 requested, 0 available). Set overrideCap: true to bypass." }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v2/me/tournaments": {
      "get": {
        "operationId": "getMyTournaments",
        "summary": "My Tournaments",
        "description": "Tournaments owned by your API key. Defaults to tournaments with a future start date; pass `filter=all` to include in-progress and past events.\n\nRequires an active TopDeck subscription.",
        "tags": ["Account"],
        "parameters": [
          {
            "name": "filter",
            "in": "query",
            "description": "Set to `all` to include in-progress and past tournaments. Default returns only tournaments with a future start date.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": ["upcoming", "all"],
              "default": "upcoming"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of tournaments owned by the API key holder.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MyTournament"
                  }
                },
                "example": [
                  {
                    "tid": "abc123",
                    "name": "Weekly Modern",
                    "game": "Magic: The Gathering",
                    "format": "Modern",
                    "startDate": 1627844461,
                    "endDate": 1627855261,
                    "status": "Not Started",
                    "location": {
                      "name": "Card Kingdom Seattle",
                      "city": "Seattle",
                      "state": "WA",
                      "country": "US"
                    },
                    "headerImage": "https://...",
                    "registeredCount": 24,
                    "capacity": 32
                  }
                ]
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Your TopDeck.gg API key. Get one free at https://topdeck.gg/account"
      }
    },
    "parameters": {
      "TID": {
        "name": "TID",
        "in": "path",
        "required": true,
        "description": "Tournament identifier.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "BulkTournamentRequest": {
        "type": "object",
        "description": "Request body for bulk tournament search. Use either TID mode (specific IDs) or query mode (game/format/date filters).",
        "properties": {
          "TID": {
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "string" } }
            ],
            "description": "Tournament ID or array of IDs. When provided, game/format/date filters are ignored."
          },
          "game": {
            "type": "string",
            "description": "Game name for filtering. Case sensitive. Example: \"Magic: The Gathering\", \"Pokemon\", \"Yu-Gi-Oh!\""
          },
          "format": {
            "type": "string",
            "description": "Game format for filtering. Case sensitive. Example: \"Standard\", \"EDH\", \"Modern\""
          },
          "start": {
            "type": "integer",
            "description": "Earliest tournament start date to include (unix seconds)."
          },
          "end": {
            "type": "integer",
            "description": "Latest tournament start date to include (unix seconds)."
          },
          "last": {
            "type": "integer",
            "description": "Number of days back from today to include. Alternative to start/end."
          },
          "participantMin": {
            "type": "integer",
            "description": "Minimum number of participants."
          },
          "participantMax": {
            "type": "integer",
            "description": "Maximum number of participants."
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["name", "decklist", "wins", "winsSwiss", "winsBracket", "winRate", "winRateSwiss", "winRateBracket", "byes", "draws", "losses", "lossesSwiss", "lossesBracket", "id"]
            },
            "default": ["decklist", "wins", "draws", "losses"],
            "description": "Player columns to include in standings."
          },
          "rounds": {
            "oneOf": [
              { "type": "boolean" },
              { "type": "array", "items": { "type": "string" } }
            ],
            "default": false,
            "description": "Round details to include. `true` returns [\"round\", \"tables\"]. Array for custom fields."
          },
          "tables": {
            "type": "array",
            "items": { "type": "string" },
            "default": ["table", "players", "winner", "status"],
            "description": "Table details to include within rounds."
          },
          "players": {
            "type": "array",
            "items": { "type": "string" },
            "default": ["name", "id"],
            "description": "Player details within round tables."
          }
        }
      },
      "BulkTournament": {
        "type": "object",
        "properties": {
          "TID": {
            "type": "string",
            "description": "Tournament identifier."
          },
          "tournamentName": {
            "type": "string",
            "description": "Name of the tournament."
          },
          "swissNum": {
            "type": "number",
            "description": "Number of Swiss rounds played."
          },
          "startDate": {
            "type": "number",
            "description": "Unix timestamp of tournament start."
          },
          "game": {
            "type": "string",
            "description": "Game being played."
          },
          "format": {
            "type": "string",
            "description": "Game format."
          },
          "topCut": {
            "type": "number",
            "description": "Size of the top cut bracket (0 if none)."
          },
          "eventData": {
            "$ref": "#/components/schemas/EventData"
          },
          "standings": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "Player standing. Fields depend on the `columns` request parameter."
            },
            "description": "Player standings based on requested columns."
          },
          "rounds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Round"
            },
            "description": "Round data. Only included when `rounds` parameter is set."
          }
        }
      },
      "MyTournament": {
        "type": "object",
        "properties": {
          "tid": {
            "type": "string",
            "description": "Tournament ID."
          },
          "name": {
            "type": "string",
            "description": "Tournament name."
          },
          "game": {
            "type": "string",
            "description": "Game being played."
          },
          "format": {
            "type": "string",
            "description": "Game format."
          },
          "startDate": {
            "type": "number",
            "description": "Unix timestamp of start."
          },
          "endDate": {
            "type": ["number", "null"],
            "description": "Unix timestamp of end."
          },
          "status": {
            "type": "string",
            "enum": ["Complete", "Ongoing", "Not Started"],
            "description": "Tournament status."
          },
          "location": {
            "oneOf": [
              { "$ref": "#/components/schemas/Location" },
              { "type": "null" }
            ],
            "description": "Location details."
          },
          "headerImage": {
            "type": ["string", "null"],
            "description": "URL of event header image."
          },
          "registeredCount": {
            "type": ["number", "null"],
            "description": "Current registered player count (null if event has no registration data)."
          },
          "capacity": {
            "type": ["number", "null"],
            "description": "Participant cap for the event (null if uncapped)."
          }
        }
      },
      "FullTournament": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "tid": { "type": "string" },
              "name": { "type": "string" },
              "game": { "type": "string" },
              "format": { "type": "string" },
              "startDate": { "type": "number" }
            },
            "description": "Tournament metadata."
          },
          "standings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Standing"
            },
            "description": "Player standings with detailed stats."
          },
          "rounds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Round"
            },
            "description": "All rounds with table details."
          }
        }
      },
      "TournamentInfo": {
        "type": "object",
        "properties": {
          "tid": { "type": "string", "description": "Tournament ID." },
          "name": { "type": "string", "description": "Tournament name." },
          "game": { "type": "string", "description": "Game being played." },
          "format": { "type": "string", "description": "Game format." },
          "startDate": { "type": "number", "description": "Unix timestamp of start." },
          "endDate": { "type": ["number", "null"], "description": "Unix timestamp of end." },
          "status": {
            "type": "string",
            "enum": ["Complete", "Ongoing", "Not Started"],
            "description": "Tournament status."
          },
          "location": {
            "oneOf": [
              { "$ref": "#/components/schemas/Location" },
              { "type": "null" }
            ],
            "description": "Location details."
          },
          "headerImage": { "type": ["string", "null"], "description": "URL of event header image." }
        }
      },
      "Standing": {
        "type": "object",
        "properties": {
          "standing": { "type": "number", "description": "Standing position." },
          "name": { "type": "string", "description": "Display name." },
          "id": { "type": "string", "description": "Player ID." },
          "discord": { "type": ["string", "null"], "description": "Discord username. Only when the tournament includes Discord info." },
          "discordId": { "type": ["string", "null"], "description": "Discord user ID. Only when the tournament includes Discord info." },
          "decklist": { "type": "string", "description": "Decklist text or URL. Conditional." },
          "deckObj": { "type": "object", "description": "Structured deck data. Conditional." },
          "points": { "type": "number", "description": "Total match points." },
          "winRate": { "type": "number", "description": "Match win rate (0.0–1.0). Standard tournaments." },
          "opponentWinRate": { "type": "number", "description": "Opponent match win rate. Standard tournaments." },
          "successRate": { "type": "number", "description": "Success rate (0.0–1.0). League tournaments." },
          "opponentSuccessRate": { "type": "number", "description": "Opponent success rate. League tournaments." },
          "gameWinRate": { "type": "number", "description": "Game win rate. Pairs mode only." },
          "opponentGameWinRate": { "type": "number", "description": "Opponent game win rate. Pairs mode only." }
        }
      },
      "PlayerDetail": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "description": "Display name." },
          "standing": { "type": "number", "description": "Tournament standing." },
          "decklist": { "type": "string", "description": "Decklist text or URL. Conditional." },
          "deckObj": { "type": "object", "description": "Structured deck data. Conditional." },
          "winRate": { "type": "number", "description": "Overall win rate (0.0–1.0)." },
          "gamesPlayed": { "type": "number", "description": "Total games played." },
          "gamesWon": { "type": "number", "description": "Total games won." },
          "byes": { "type": "number", "description": "Byes received." },
          "gamesDrawn": { "type": "number", "description": "Total draws." },
          "gamesLost": { "type": "number", "description": "Total losses." }
        }
      },
      "Round": {
        "type": "object",
        "properties": {
          "round": {
            "oneOf": [
              { "type": "number" },
              { "type": "string" }
            ],
            "description": "Round number or bracket label (e.g. \"Top 8\")."
          },
          "tables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Table"
            }
          }
        }
      },
      "Table": {
        "type": "object",
        "properties": {
          "table": {
            "oneOf": [
              { "type": "number" },
              { "type": "string" }
            ],
            "description": "Table number or \"Byes\" for bye assignments."
          },
          "players": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string" },
                "id": { "type": "string" }
              }
            }
          },
          "winner": {
            "type": ["string", "null"],
            "description": "Winner's name. Null for draws or unfinished."
          },
          "winner_id": {
            "type": ["string", "null"],
            "description": "Winner's ID, \"Draw\" for ties, null for unfinished."
          },
          "status": {
            "type": "string",
            "enum": ["Completed", "Active", "Pending", "Bye"],
            "description": "Match status."
          }
        }
      },
      "EventData": {
        "type": "object",
        "properties": {
          "lat": { "type": "number", "description": "Latitude." },
          "lng": { "type": "number", "description": "Longitude." },
          "city": { "type": "string", "description": "City name." },
          "state": { "type": "string", "description": "State/province." },
          "address": { "type": "string", "description": "Organizer-entered location string — venue name, full address, or \"Online\"." },
          "headerImage": { "type": "string", "description": "Event header image URL." }
        }
      },
      "Location": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "description": "Venue name." },
          "city": { "type": "string", "description": "City." },
          "state": { "type": "string", "description": "State/province." },
          "country": { "type": "string", "description": "Country code." },
          "lat": { "type": "number", "description": "Latitude." },
          "lng": { "type": "number", "description": "Longitude." }
        }
      },
      "RegisterByEmailResult": {
        "type": "object",
        "description": "Per-email outcomes. Each input email appears in exactly one bucket, lowercased.",
        "properties": {
          "added": {
            "type": "array",
            "items": { "type": "string", "format": "email" },
            "description": "Newly registered."
          },
          "pending": {
            "type": "array",
            "items": { "type": "string", "format": "email" },
            "description": "No account found; invitation email sent. Converts to a registration on signup."
          },
          "alreadyRegistered": {
            "type": "array",
            "items": { "type": "string", "format": "email" },
            "description": "Already registered or already invited. No-op."
          },
          "failed": {
            "type": "array",
            "items": { "type": "string", "format": "email" },
            "description": "Could not be processed."
          },
          "banned": {
            "type": "array",
            "items": { "type": "string", "format": "email" },
            "description": "Banned by this organizer. Skipped."
          },
          "capacity": {
            "type": ["integer", "null"],
            "description": "Event player cap, or `null` if unset."
          },
          "registeredCount": {
            "type": "integer",
            "description": "Total registered: confirmed players, pending invites, and offered waitlist seats."
          }
        }
      },
      "Attendee": {
        "type": "object",
        "properties": {
          "uid": { "type": ["string", "null"], "description": "User's unique identifier." },
          "name": { "type": "string", "description": "Display name." },
          "email": { "type": ["string", "null"], "description": "Email address." },
          "discord": { "type": ["string", "null"], "description": "Discord username." },
          "discordId": { "type": ["string", "null"], "description": "Discord user ID." },
          "status": {
            "type": "string",
            "enum": ["player", "dropped", "waitlist"],
            "description": "Attendee status."
          },
          "standing": { "type": ["number", "null"], "description": "Tournament standing. Null for dropped/waitlist." },
          "decklist": { "type": ["string", "null"], "description": "Decklist URL if available." },
          "deckObj": { "type": ["object", "null"], "description": "Structured deck data." },
          "joinedAt": { "type": ["number", "null"], "description": "Unix timestamp when user joined waitlist. Waitlist only." },
          "waitlistStatus": {
            "type": "string",
            "enum": ["waiting", "offered", "accepted", "declined", "expired"],
            "description": "Waitlist sub-status. Waitlist only."
          },
          "waitlistPosition": { "type": ["number", "null"], "description": "Position in queue. Only for \"waiting\" status." },
          "offeredAt": { "type": ["number", "null"], "description": "When spot was offered. Waitlist only." },
          "expirationTimestamp": { "type": ["number", "null"], "description": "When offer expires. Waitlist only." }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error message."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Missing or invalid parameters.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Both \"game\" and \"format\" fields are required." }
          }
        }
      },
      "Unauthorized": {
        "description": "Invalid or missing API key.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Invalid API key" }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests per minute. The response includes a `Retry-After` header with the number of seconds to wait.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Rate limit exceeded", "retryAfterSeconds": 42 }
          }
        }
      },
      "ServerError": {
        "description": "Internal server error.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Internal server error" }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Tournaments",
      "description": "Public tournament data — search, standings, rounds, and players."
    },
    {
      "name": "Staff",
      "description": "Actions on tournaments where the API key holder has a staff role. Required role varies by endpoint — see each endpoint's description."
    },
    {
      "name": "Account",
      "description": "Endpoints scoped to the authenticated API key holder."
    }
  ]
}