{
  "openapi": "3.1.0",
  "info": {
    "title": "ChessUrge Public API",
    "summary": "Read-only public data for games, tournaments and players.",
    "description": "Public, read-only endpoints for ChessUrge. No account, API key or authentication is required.\n\nOnly completed platform games are exposed. Uploaded games, private notes, collections, tags, analysis data, share tokens and account details are never returned. Responses are cacheable and pagination is capped.",
    "version": "1.0.0",
    "contact": {
      "name": "ChessUrge",
      "url": "https://chessurge.org"
    }
  },
  "externalDocs": {
    "description": "Interactive developer playground",
    "url": "https://chessurge.org/developers/playground"
  },
  "servers": [
    {
      "url": "https://chessurge.org",
      "description": "Current deployment"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Games",
      "description": "Completed public platform games."
    },
    {
      "name": "Tournaments",
      "description": "Public tournament listings, standings and pairings."
    },
    {
      "name": "Players",
      "description": "Public player profiles and aggregate statistics."
    }
  ],
  "paths": {
    "/api/public/games": {
      "get": {
        "tags": [
          "Games"
        ],
        "operationId": "listPublicGames",
        "summary": "List public games",
        "description": "Returns completed games played on ChessUrge, newest first. Use player to filter by the exact username of either colour and result to filter by the final result.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of games per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "result",
            "in": "query",
            "description": "Filter by the final result. Omit for all results.",
            "schema": {
              "type": "string",
              "enum": [
                "1-0",
                "0-1",
                "1/2-1/2"
              ]
            }
          },
          {
            "name": "player",
            "in": "query",
            "description": "Exact, case-insensitive username of the white or black player.",
            "schema": {
              "type": "string",
              "maxLength": 40
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of public games.",
            "headers": {
              "Cache-Control": {
                "description": "Public cache policy for this response.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamesResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/public/tournaments": {
      "get": {
        "tags": [
          "Tournaments"
        ],
        "operationId": "listPublicTournaments",
        "summary": "List tournaments",
        "description": "Returns public tournaments with entry and match counts, newest start time first.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number, starting at 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10000,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of tournaments per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by tournament status. Omit for all statuses.",
            "schema": {
              "type": "string",
              "enum": [
                "upcoming",
                "live",
                "finished"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Case-insensitive search over tournament names.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of public tournaments.",
            "headers": {
              "Cache-Control": {
                "description": "Public cache policy for this response.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TournamentListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/api/public/tournaments/{slug}": {
      "get": {
        "tags": [
          "Tournaments"
        ],
        "operationId": "getPublicTournament",
        "summary": "Get a tournament",
        "description": "Returns one tournament with its entry count, current standings and public match results. Disputed result reports remain private.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Tournament slug.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tournament details, standings and matches.",
            "headers": {
              "Cache-Control": {
                "description": "Public cache policy for this response.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TournamentDetailResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/public/players/{username}": {
      "get": {
        "tags": [
          "Players"
        ],
        "operationId": "getPublicPlayer",
        "summary": "Get a player profile",
        "description": "Returns a public profile, current ratings, aggregate game statistics, recent public games and aggregate puzzle statistics.",
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "required": true,
            "description": "Player username. The lookup is case-insensitive.",
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 24,
              "pattern": "^[A-Za-z0-9_]+$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of recent games to include.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public player profile and statistics.",
            "headers": {
              "Cache-Control": {
                "description": "Public cache policy for this response.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Pagination": {
        "type": "object",
        "required": [
          "page",
          "limit",
          "total",
          "totalPages",
          "hasNext",
          "hasPrevious"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1
          },
          "limit": {
            "type": "integer",
            "minimum": 1
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "totalPages": {
            "type": "integer",
            "minimum": 0
          },
          "hasNext": {
            "type": "boolean"
          },
          "hasPrevious": {
            "type": "boolean"
          }
        }
      },
      "GamePlayer": {
        "type": "object",
        "required": [
          "username",
          "displayName",
          "rating"
        ],
        "properties": {
          "username": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "rating": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Game": {
        "type": "object",
        "required": [
          "id",
          "white",
          "black",
          "result",
          "timeControl",
          "timeControlSeconds",
          "incrementSeconds",
          "rated",
          "pgn",
          "fen",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "white": {
            "$ref": "#/components/schemas/GamePlayer"
          },
          "black": {
            "$ref": "#/components/schemas/GamePlayer"
          },
          "result": {
            "type": "string",
            "enum": [
              "1-0",
              "0-1",
              "1/2-1/2"
            ]
          },
          "timeControl": {
            "type": [
              "string",
              "null"
            ]
          },
          "timeControlSeconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "incrementSeconds": {
            "type": "integer"
          },
          "rated": {
            "type": "boolean"
          },
          "pgn": {
            "type": "string"
          },
          "fen": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "GamesResponse": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Tournament": {
        "type": "object",
        "required": [
          "name",
          "slug",
          "description",
          "format",
          "timeControl",
          "startsAt",
          "status",
          "createdAt",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "format": {
            "type": "string"
          },
          "timeControl": {
            "type": "string"
          },
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "upcoming",
              "live",
              "finished"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "TournamentSummary": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Tournament"
          },
          {
            "type": "object",
            "required": [
              "entriesCount",
              "matchesCount"
            ],
            "properties": {
              "entriesCount": {
                "type": "integer",
                "minimum": 0
              },
              "matchesCount": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        ]
      },
      "TournamentListResponse": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TournamentSummary"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Standing": {
        "type": "object",
        "required": [
          "rank",
          "username",
          "displayName",
          "rating",
          "points",
          "played",
          "wins",
          "draws",
          "losses",
          "buchholz"
        ],
        "properties": {
          "rank": {
            "type": "integer",
            "minimum": 1
          },
          "username": {
            "type": "string"
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating": {
            "type": "integer"
          },
          "points": {
            "type": "number"
          },
          "played": {
            "type": "integer",
            "minimum": 0
          },
          "wins": {
            "type": "integer",
            "minimum": 0
          },
          "draws": {
            "type": "integer",
            "minimum": 0
          },
          "losses": {
            "type": "integer",
            "minimum": 0
          },
          "buchholz": {
            "type": "number"
          }
        }
      },
      "TournamentMatchPlayer": {
        "type": "object",
        "required": [
          "username",
          "displayName",
          "rating"
        ],
        "properties": {
          "username": {
            "type": "string"
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ]
          },
          "rating": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "TournamentMatch": {
        "type": "object",
        "required": [
          "round",
          "white",
          "black",
          "result",
          "status"
        ],
        "properties": {
          "round": {
            "type": "integer",
            "minimum": 1
          },
          "white": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TournamentMatchPlayer"
              },
              {
                "type": "null"
              }
            ]
          },
          "black": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TournamentMatchPlayer"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "string",
            "enum": [
              "1-0",
              "0-1",
              "1/2-1/2",
              "*"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "finished",
              "disputed",
              "bye"
            ]
          }
        }
      },
      "TournamentDetail": {
        "type": "object",
        "required": [
          "tournament",
          "entriesCount",
          "matchesCount",
          "standings",
          "matches"
        ],
        "properties": {
          "tournament": {
            "$ref": "#/components/schemas/Tournament"
          },
          "entriesCount": {
            "type": "integer",
            "minimum": 0
          },
          "matchesCount": {
            "type": "integer",
            "minimum": 0
          },
          "standings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Standing"
            }
          },
          "matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TournamentMatch"
            }
          }
        }
      },
      "TournamentDetailResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/TournamentDetail"
          }
        }
      },
      "PlayerRatings": {
        "type": "object",
        "required": [
          "overall",
          "puzzle"
        ],
        "properties": {
          "overall": {
            "type": "integer"
          },
          "puzzle": {
            "type": "integer"
          }
        }
      },
      "PlayerStats": {
        "type": "object",
        "required": [
          "games",
          "wins",
          "draws",
          "losses",
          "winRate",
          "score"
        ],
        "properties": {
          "games": {
            "type": "integer",
            "minimum": 0
          },
          "wins": {
            "type": "integer",
            "minimum": 0
          },
          "draws": {
            "type": "integer",
            "minimum": 0
          },
          "losses": {
            "type": "integer",
            "minimum": 0
          },
          "winRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          }
        }
      },
      "PuzzleStats": {
        "type": "object",
        "required": [
          "attempts",
          "solved",
          "distinctSolved",
          "accuracy",
          "averageSeconds",
          "lastAttemptAt"
        ],
        "properties": {
          "attempts": {
            "type": "integer",
            "minimum": 0
          },
          "solved": {
            "type": "integer",
            "minimum": 0
          },
          "distinctSolved": {
            "type": "integer",
            "minimum": 0
          },
          "accuracy": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "averageSeconds": {
            "type": "number",
            "minimum": 0
          },
          "lastAttemptAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "PlayerProfile": {
        "type": "object",
        "required": [
          "username",
          "displayName",
          "avatarUrl",
          "countryCode",
          "memberSince",
          "profileUrl",
          "ratings",
          "stats",
          "puzzleStats",
          "recentGames"
        ],
        "properties": {
          "username": {
            "type": "string"
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ]
          },
          "avatarUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "countryCode": {
            "type": [
              "string",
              "null"
            ]
          },
          "memberSince": {
            "type": "string",
            "format": "date-time"
          },
          "profileUrl": {
            "type": "string"
          },
          "ratings": {
            "$ref": "#/components/schemas/PlayerRatings"
          },
          "stats": {
            "$ref": "#/components/schemas/PlayerStats"
          },
          "puzzleStats": {
            "$ref": "#/components/schemas/PuzzleStats"
          },
          "recentGames": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          }
        }
      },
      "PlayerResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PlayerProfile"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid query or path parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "The requested tournament or player does not exist.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}