{
  "openapi": "3.1.0",
  "info": {
    "title": "GamesVS Game Tracking API",
    "version": "1.0.0",
    "description": "GamesVS game integration API. Use this schema to connect a game with leaderboards, player statistics, achievements, and completed-run tracking. Register your game at https://gamesvs.com/developers/. Server tokens are game-scoped secrets and must remain on your backend. Official submissions require an existing GamesVS player ID securely linked to your authenticated player; automatic external account linking is not available. Direct browser integration requires a GamesVS-origin login session. See the human guide and downloadable AI setup instructions at https://gamesvs.com/api-guide/."
  },
  "servers": [
    {
      "url": "https://api.gamesvs.com/v1",
      "description": "Live production API; configure a game and player before integrating."
    }
  ],
  "tags": [
    {
      "name": "Tracking",
      "description": "Game tracking, public stats, leaderboards, and achievements."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getVersion",
        "summary": "Get API version",
        "tags": [
          "Tracking"
        ],
        "description": "Get API version",
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionResponse"
                },
                "example": {
                  "data": {
                    "version": "v1",
                    "name": "GamesVS Game Tracking"
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          }
        }
      }
    },
    "/games": {
      "get": {
        "operationId": "listGames",
        "summary": "List active games",
        "tags": [
          "Tracking"
        ],
        "description": "Games are ordered by slug. An empty installation returns an empty games list.",
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GameListResponse"
                },
                "example": {
                  "data": {
                    "games": [
                      {
                        "id": "wizards",
                        "title": "Wizards"
                      }
                    ],
                    "pagination": {
                      "limit": 25,
                      "offset": 0,
                      "total": 1
                    }
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ]
      }
    },
    "/games/{game}": {
      "get": {
        "operationId": "getGame",
        "summary": "Get active game metadata",
        "tags": [
          "Tracking"
        ],
        "description": "Get active game metadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GameResponse"
                },
                "example": {
                  "data": {
                    "id": "wizards",
                    "title": "Wizards"
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Game"
          }
        ]
      }
    },
    "/players/me": {
      "get": {
        "operationId": "getCurrentPlayer",
        "summary": "Get signed-in player and CSRF token",
        "tags": [
          "Tracking"
        ],
        "description": "Requires an existing signed-in GamesVS browser session. Creates the public profile on first use. A server Bearer key alone cannot authenticate this endpoint. Public account signup is available at /developers/. External identity handoff is not implemented.",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentPlayerResponse"
                },
                "example": {
                  "data": {
                    "player": {
                      "id": "0123456789abcdef0123456789abcdef",
                      "display_name": "Player 01234567"
                    },
                    "csrf_token": "SESSION_CSRF_TOKEN"
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/games/{game}/leaderboard": {
      "get": {
        "operationId": "getLeaderboard",
        "summary": "Get official best-score leaderboard",
        "tags": [
          "Tracking"
        ],
        "description": "One entry per player using their highest verified score. Descending order; equal scores share competition rank (1, 1, 3), with public player ID breaking ordering ties. Rank is computed before pagination. No seasons or alternate metrics.",
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaderboardResponse"
                },
                "example": {
                  "data": {
                    "game": "wizards",
                    "metric": "best_score",
                    "entries": [
                      {
                        "rank": 1,
                        "score": 1250,
                        "player": {
                          "id": "0123456789abcdef0123456789abcdef",
                          "display_name": "Player 01234567"
                        }
                      }
                    ],
                    "pagination": {
                      "limit": 25,
                      "offset": 0,
                      "total": 1
                    }
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Game"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Offset"
          }
        ]
      }
    },
    "/players/{player}/games/{game}/stats": {
      "get": {
        "operationId": "getPlayerStats",
        "summary": "Get official or private unverified stats",
        "tags": [
          "Tracking"
        ],
        "description": "Verified stats are public. scope=unverified requires the owning GamesVS player session; a Bearer server key does not grant access. Named totals are decimal strings. With no runs: run_count=0, best_score=null, stats={}.",
        "security": [
          {},
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerStatsResponse"
                },
                "example": {
                  "data": {
                    "player": {
                      "id": "0123456789abcdef0123456789abcdef",
                      "display_name": "Player 01234567"
                    },
                    "game": "wizards",
                    "verified": true,
                    "run_count": 1,
                    "best_score": 1250,
                    "stats": {
                      "coins_collected": "40",
                      "enemies_defeated": "12"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Player"
          },
          {
            "$ref": "#/components/parameters/Game"
          },
          {
            "name": "scope",
            "in": "query",
            "description": "unverified is private to the signed-in owner.",
            "schema": {
              "type": "string",
              "enum": [
                "verified",
                "unverified"
              ],
              "default": "verified"
            }
          }
        ]
      }
    },
    "/players/{player}/games/{game}/achievements": {
      "get": {
        "operationId": "getPlayerAchievements",
        "summary": "Get active achievement definitions and unlock state",
        "tags": [
          "Tracking"
        ],
        "description": "Active definitions ordered by code. Locked achievements have unlocked_at=null. Only new trusted runs evaluate unlocks; browser runs and duplicate retries do not. No automatic historical backfill after editing definitions.",
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerAchievementsResponse"
                },
                "example": {
                  "data": {
                    "player": {
                      "id": "0123456789abcdef0123456789abcdef",
                      "display_name": "Player 01234567"
                    },
                    "game": "wizards",
                    "achievements": [
                      {
                        "code": "score-1000",
                        "title": "Score 1000",
                        "description": "Finish a trusted run with at least 1000 points.",
                        "metric": "best_score",
                        "stat_key": null,
                        "threshold": 1000,
                        "unlocked_at": "2026-10-05T18:00:00Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Player"
          },
          {
            "$ref": "#/components/parameters/Game"
          }
        ]
      }
    },
    "/games/{game}/runs": {
      "post": {
        "operationId": "submitRun",
        "summary": "Submit a completed browser or server run",
        "tags": [
          "Tracking"
        ],
        "description": "New runs return 201. Identical retries return 200 with the original run ID and replayed=true. Identity is game + player + trust scope + submission_id. Changed content returns 409. Browser and trusted submissions are separate records; trusted submissions do not promote browser records. Authorization, if supplied, is always checked as a game API key and does not fall back to session auth. Verified means submitted by an authorized server, not independently validated gameplay.",
        "security": [
          {
            "serverKey": []
          },
          {
            "cookieAuth": [],
            "csrfHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Identical retry: original run, replayed=true.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunResultResponse"
                },
                "example": {
                  "data": {
                    "id": "abcdef0123456789abcdef0123456789",
                    "submission_id": "match-2026-001-player-42",
                    "verified": true,
                    "score": 1250,
                    "replayed": true,
                    "unlocked_achievements": [
                      "score-1000"
                    ]
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "default": {
            "$ref": "#/components/responses/UnexpectedError"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "415": {
            "$ref": "#/components/responses/WrongContentType"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "201": {
            "description": "New run accepted; replayed=false.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string"
                },
                "description": "API controller responses use no-store."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunResultResponse"
                },
                "example": {
                  "data": {
                    "id": "abcdef0123456789abcdef0123456789",
                    "submission_id": "match-2026-001-player-42",
                    "verified": true,
                    "score": 1250,
                    "replayed": false,
                    "unlocked_achievements": [
                      "score-1000"
                    ]
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/Game"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Maximum 65536 bytes. Required payload variant depends on authentication. Unknown fields are rejected.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunSubmission"
              },
              "examples": {
                "browser": {
                  "summary": "Browser session + X-SecurityID; no player_id",
                  "value": {
                    "submission_id": "browser-run-001",
                    "score": 1250,
                    "stats": {
                      "enemies_defeated": 12,
                      "coins_collected": 40
                    }
                  }
                },
                "server": {
                  "summary": "Game server Bearer key + player_id",
                  "value": {
                    "player_id": "0123456789abcdef0123456789abcdef",
                    "submission_id": "match-2026-001-player-42",
                    "score": 1250,
                    "stats": {
                      "enemies_defeated": 12,
                      "coins_collected": 40
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Counter": {
        "type": "integer",
        "minimum": 0,
        "maximum": 1000000000000,
        "description": "Nonnegative integer JSON literal. The PHP implementation rejects decimal/scientific notation and numeric strings."
      },
      "PlayerID": {
        "type": "string",
        "pattern": "^[a-f0-9]{32}$",
        "description": "Public player identifier, not a credential or Silverstripe Member ID."
      },
      "SubmissionID": {
        "type": "string",
        "pattern": "^[A-Za-z0-9_-]{1,64}$",
        "description": "Persist once per completed run and reuse unchanged on retries."
      },
      "Player": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/PlayerID"
          },
          "display_name": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "display_name"
        ]
      },
      "Game": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Game slug, such as wizards."
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "title"
        ]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000
          },
          "total": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "limit",
          "offset",
          "total"
        ]
      },
      "Version": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string",
            "const": "v1"
          },
          "name": {
            "type": "string"
          }
        },
        "required": [
          "version",
          "name"
        ]
      },
      "GameList": {
        "type": "object",
        "properties": {
          "games": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "required": [
          "games",
          "pagination"
        ]
      },
      "CurrentPlayer": {
        "type": "object",
        "properties": {
          "player": {
            "$ref": "#/components/schemas/Player"
          },
          "csrf_token": {
            "type": "string",
            "description": "Session CSRF token sent in X-SecurityID on browser POSTs."
          }
        },
        "required": [
          "player",
          "csrf_token"
        ]
      },
      "RunCounters": {
        "type": "object",
        "maxProperties": 32,
        "propertyNames": {
          "pattern": "^[a-z][a-z0-9_]{0,63}$"
        },
        "additionalProperties": {
          "$ref": "#/components/schemas/Counter"
        },
        "description": "Per-run counters, not lifetime totals. Omit or use an empty object; arrays and null are rejected."
      },
      "BrowserRun": {
        "type": "object",
        "properties": {
          "submission_id": {
            "$ref": "#/components/schemas/SubmissionID"
          },
          "score": {
            "$ref": "#/components/schemas/Counter"
          },
          "stats": {
            "$ref": "#/components/schemas/RunCounters"
          }
        },
        "required": [
          "submission_id",
          "score"
        ],
        "additionalProperties": false
      },
      "ServerRun": {
        "type": "object",
        "properties": {
          "submission_id": {
            "$ref": "#/components/schemas/SubmissionID"
          },
          "score": {
            "$ref": "#/components/schemas/Counter"
          },
          "stats": {
            "$ref": "#/components/schemas/RunCounters"
          },
          "player_id": {
            "$ref": "#/components/schemas/PlayerID"
          }
        },
        "required": [
          "submission_id",
          "score",
          "player_id"
        ],
        "additionalProperties": false
      },
      "RunSubmission": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/BrowserRun"
          },
          {
            "$ref": "#/components/schemas/ServerRun"
          }
        ],
        "description": "Without Authorization, use BrowserRun with a session cookie and X-SecurityID. With Authorization: Bearer, use ServerRun with player_id. These combinations are conditional and must be preserved by clients."
      },
      "RunResult": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/RunID"
          },
          "submission_id": {
            "$ref": "#/components/schemas/SubmissionID"
          },
          "verified": {
            "type": "boolean"
          },
          "score": {
            "$ref": "#/components/schemas/Counter"
          },
          "replayed": {
            "type": "boolean"
          },
          "unlocked_achievements": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "submission_id",
          "verified",
          "score",
          "replayed",
          "unlocked_achievements"
        ]
      },
      "LeaderboardEntry": {
        "type": "object",
        "properties": {
          "rank": {
            "type": "integer",
            "minimum": 1
          },
          "score": {
            "$ref": "#/components/schemas/Counter"
          },
          "player": {
            "$ref": "#/components/schemas/Player"
          }
        },
        "required": [
          "rank",
          "score",
          "player"
        ]
      },
      "Leaderboard": {
        "type": "object",
        "properties": {
          "game": {
            "type": "string"
          },
          "metric": {
            "type": "string",
            "const": "best_score"
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeaderboardEntry"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "required": [
          "game",
          "metric",
          "entries",
          "pagination"
        ]
      },
      "PlayerStats": {
        "type": "object",
        "properties": {
          "player": {
            "$ref": "#/components/schemas/Player"
          },
          "game": {
            "type": "string"
          },
          "verified": {
            "type": "boolean"
          },
          "run_count": {
            "type": "integer",
            "minimum": 0
          },
          "best_score": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Counter"
              },
              {
                "type": "null"
              }
            ]
          },
          "stats": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "description": "Exact cumulative totals represented as decimal strings; no runs produces {}."
          }
        },
        "required": [
          "player",
          "game",
          "verified",
          "run_count",
          "best_score",
          "stats"
        ]
      },
      "Achievement": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "metric": {
            "type": "string",
            "enum": [
              "best_score",
              "run_count",
              "stat_total"
            ]
          },
          "stat_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "threshold": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000000000000
          },
          "unlocked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "UTC unlock timestamp, or null while locked."
          }
        },
        "required": [
          "code",
          "title",
          "description",
          "metric",
          "stat_key",
          "threshold",
          "unlocked_at"
        ]
      },
      "PlayerAchievements": {
        "type": "object",
        "properties": {
          "player": {
            "$ref": "#/components/schemas/Player"
          },
          "game": {
            "type": "string"
          },
          "achievements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Achievement"
            }
          }
        },
        "required": [
          "player",
          "game",
          "achievements"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "VersionResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Version"
          }
        },
        "required": [
          "data"
        ]
      },
      "GameResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Game"
          }
        },
        "required": [
          "data"
        ]
      },
      "GameListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/GameList"
          }
        },
        "required": [
          "data"
        ]
      },
      "CurrentPlayerResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/CurrentPlayer"
          }
        },
        "required": [
          "data"
        ]
      },
      "RunResultResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/RunResult"
          }
        },
        "required": [
          "data"
        ]
      },
      "LeaderboardResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Leaderboard"
          }
        },
        "required": [
          "data"
        ]
      },
      "PlayerStatsResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PlayerStats"
          }
        },
        "required": [
          "data"
        ]
      },
      "PlayerAchievementsResponse": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/PlayerAchievements"
          }
        },
        "required": [
          "data"
        ]
      },
      "RunID": {
        "type": "string",
        "pattern": "^[a-f0-9]{32}$",
        "description": "Public ID of the accepted run, distinct from the caller-provided submission_id."
      }
    },
    "parameters": {
      "Game": {
        "name": "game",
        "in": "path",
        "required": true,
        "description": "Active game slug configured in the CMS.",
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
        }
      },
      "Player": {
        "name": "player",
        "in": "path",
        "required": true,
        "description": "Existing public player ID; never an email or internal Member ID.",
        "schema": {
          "$ref": "#/components/schemas/PlayerID"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        },
        "description": "Decimal digits only, maximum six characters."
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "maximum": 100000,
          "default": 0
        },
        "description": "Decimal digits only, maximum six characters."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "400 invalid_json: malformed JSON.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401 authentication_required or invalid_api_key.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "403 csrf_failed, wrong_game, or private_stats, depending on the endpoint.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "404 game_not_found, player_not_found, or not_found, depending on the endpoint.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "405 method_not_allowed.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          },
          "Allow": {
            "schema": {
              "type": "string"
            },
            "description": "Expected HTTP method."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "409 submission_conflict: different content under the same submission identity.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooLarge": {
        "description": "413 payload_too_large: body exceeds 65536 bytes.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "WrongContentType": {
        "description": "415 json_required: use Content-Type: application/json.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "422 invalid_payload, invalid_pagination, or invalid_scope.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "429 rate_limited: 600 requests per IP per minute across API endpoints.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          },
          "Retry-After": {
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Seconds before retrying."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InternalError": {
        "description": "500 internal_error. Pre-controller framework or proxy errors may be non-JSON.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "UnexpectedError": {
        "description": "An unexpected HTTP error. Before the API controller, the response may be HTML instead of JSON.",
        "headers": {
          "Cache-Control": {
            "schema": {
              "type": "string"
            },
            "description": "API controller responses use no-store."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "serverKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Opaque game-scoped API token, not JWT. Trusted run submission only; it does not identify a player. Token contains 32\u2013256 letters, numbers, underscores or hyphens. Keep on the backend."
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "PHPSESSID",
        "description": "Signed-in GamesVS browser session. Let the browser manage the cookie. A server token cannot create or replace a player session."
      },
      "csrfHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-SecurityID",
        "description": "csrf_token from /players/me. Must accompany the same owning browser session on POSTs."
      }
    }
  },
  "externalDocs": {
    "description": "Easy setup guide and AI integration instructions",
    "url": "https://gamesvs.com/api-guide/"
  }
}
