openapi: 3.0.3 info: title: Galaxy Game Lobby Service Internal REST API version: v1 description: | This specification documents the internal trusted REST contract of `galaxy/lobby` served on `LOBBY_INTERNAL_HTTP_ADDR` (default `:8095`). This port is not reachable from the public internet. Two caller classes use it: **Game Master integration paths** (`/api/v1/internal/…`): - `GET /api/v1/internal/games/{game_id}` — game detail read for `Game Master` and internal tooling - `GET /api/v1/internal/games/{game_id}/memberships` — full membership list for `Game Master` authorization checks Note: Lobby calls Game Master synchronously after a successful container start (outgoing). The `register-runtime` endpoint lives on Game Master's surface, not on Lobby's. Lobby does not accept inbound `register-runtime` requests. **Admin Service paths** (same `/api/v1/lobby/…` paths as the public port): - `Admin Service` enforces the system-admin role check at the gateway boundary before calling these endpoints - `X-User-ID` is NOT present on calls from `Admin Service`; Lobby treats all callers on this port as trusted and performs no user-level auth Transport rules: - request bodies are strict JSON only; unknown fields are rejected - error responses use `{ "error": { "code", "message" } }` - stable error codes match the public contract: `invalid_request`, `conflict`, `subject_not_found`, `forbidden`, `internal_error`, and `service_unavailable` servers: - url: http://localhost:8095 description: Default local internal listener for Game Lobby Service. tags: - name: GMIntegration description: Game Master integration paths for runtime binding and membership reads. - name: AdminGames description: Admin-mirrored game lifecycle paths called by Admin Service. - name: AdminApplications description: Admin-mirrored application approval paths called by Admin Service. - name: AdminMemberships description: Admin-mirrored membership operation paths called by Admin Service. - name: Probes description: Health and readiness probes. paths: /healthz: get: tags: - Probes operationId: internalHealthz summary: Internal listener health probe responses: "200": description: Service is alive. content: application/json: schema: $ref: "#/components/schemas/ProbeResponse" examples: ok: value: status: ok /readyz: get: tags: - Probes operationId: internalReadyz summary: Internal listener readiness probe responses: "200": description: Service is ready to serve traffic. content: application/json: schema: $ref: "#/components/schemas/ProbeResponse" examples: ready: value: status: ready /api/v1/internal/games/{game_id}: get: tags: - GMIntegration operationId: internalGetGame summary: Get one game record for Game Master or internal tooling description: | Returns the full game record without visibility restrictions. Intended for use by `Game Master` and internal administrative tooling. parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Full game record. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/internal/games/{game_id}/memberships: get: tags: - GMIntegration operationId: internalListMemberships summary: List all memberships of a game for Game Master description: | Returns all memberships of the game without visibility restrictions. Intended for `Game Master` authorization checks during command routing. Pagination applies. parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/PageSize" - $ref: "#/components/parameters/PageToken" responses: "200": description: One page of membership records. content: application/json: schema: $ref: "#/components/schemas/MembershipListResponse" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games: post: tags: - AdminGames operationId: adminCreateGame summary: Create a new game record (admin) description: | Creates a new game record in `draft` status. Used by `Admin Service` for public game creation. Lobby trusts the caller and does not enforce a user-level eligibility check on this port. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateGameRequest" responses: "201": description: Game record created in draft status. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "400": $ref: "#/components/responses/InvalidRequestError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" get: tags: - AdminGames operationId: adminListGames summary: List games (admin, unrestricted) description: | Returns a paginated list of games without visibility restrictions. Used by `Admin Service` for administrative oversight. parameters: - $ref: "#/components/parameters/PageSize" - $ref: "#/components/parameters/PageToken" responses: "200": description: One page of game records. content: application/json: schema: $ref: "#/components/schemas/GameListResponse" "400": $ref: "#/components/responses/InvalidRequestError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}: get: tags: - AdminGames operationId: adminGetGame summary: Get one game record (admin, unrestricted) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Full game record without visibility restrictions. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" patch: tags: - AdminGames operationId: adminUpdateGame summary: Update mutable fields of a game record (admin) parameters: - $ref: "#/components/parameters/GameIDPath" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateGameRequest" responses: "200": description: Updated game record. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/open-enrollment: post: tags: - AdminGames operationId: adminOpenEnrollment summary: Transition a draft game to enrollment_open (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status enrollment_open. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/ready-to-start: post: tags: - AdminGames operationId: adminManualReadyToStart summary: Manually close enrollment and transition to ready_to_start (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status ready_to_start. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/start: post: tags: - AdminGames operationId: adminStartGame summary: Initiate the game start sequence (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status starting. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/pause: post: tags: - AdminGames operationId: adminPauseGame summary: Apply a platform-level pause to a running game (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status paused. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/resume: post: tags: - AdminGames operationId: adminResumeGame summary: Resume a paused game (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status running. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/cancel: post: tags: - AdminGames operationId: adminCancelGame summary: Cancel a game that has not yet started running (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status cancelled. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/retry-start: post: tags: - AdminGames operationId: adminRetryStart summary: Retry a failed start attempt (admin) parameters: - $ref: "#/components/parameters/GameIDPath" responses: "200": description: Updated game record with status ready_to_start. content: application/json: schema: $ref: "#/components/schemas/GameRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/applications/{application_id}/approve: post: tags: - AdminApplications operationId: adminApproveApplication summary: Approve a submitted application (admin) description: | Approves a submitted application, reserves the race name, and creates an active membership. On success, `lobby.membership.approved` notification intent is published to the applicant. parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/ApplicationIDPath" responses: "200": description: Active membership created for the approved applicant. content: application/json: schema: $ref: "#/components/schemas/MembershipRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/applications/{application_id}/reject: post: tags: - AdminApplications operationId: adminRejectApplication summary: Reject a submitted application (admin) description: | Rejects a submitted application and releases any pending race name reservation. On success, `lobby.membership.rejected` notification intent is published to the applicant. parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/ApplicationIDPath" responses: "200": description: Application record with status rejected. content: application/json: schema: $ref: "#/components/schemas/ApplicationRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/memberships: get: tags: - AdminMemberships operationId: adminListMemberships summary: List memberships of a game (admin, unrestricted) parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/PageSize" - $ref: "#/components/parameters/PageToken" responses: "200": description: One page of membership records. content: application/json: schema: $ref: "#/components/schemas/MembershipListResponse" "400": $ref: "#/components/responses/InvalidRequestError" "404": $ref: "#/components/responses/NotFoundError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/memberships/{membership_id}/remove: post: tags: - AdminMemberships operationId: adminRemoveMember summary: Remove a member from a game (admin) parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/MembershipIDPath" responses: "200": description: Updated membership record with status removed. content: application/json: schema: $ref: "#/components/schemas/MembershipRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" /api/v1/lobby/games/{game_id}/memberships/{membership_id}/block: post: tags: - AdminMemberships operationId: adminBlockMember summary: Apply a platform-level block to a member (admin) parameters: - $ref: "#/components/parameters/GameIDPath" - $ref: "#/components/parameters/MembershipIDPath" responses: "200": description: Updated membership record with status blocked. content: application/json: schema: $ref: "#/components/schemas/MembershipRecord" "404": $ref: "#/components/responses/NotFoundError" "409": $ref: "#/components/responses/ConflictError" "500": $ref: "#/components/responses/InternalError" "503": $ref: "#/components/responses/ServiceUnavailableError" components: parameters: GameIDPath: name: game_id in: path required: true description: Opaque stable game identifier. schema: type: string ApplicationIDPath: name: application_id in: path required: true description: Opaque stable application identifier. schema: type: string MembershipIDPath: name: membership_id in: path required: true description: Opaque stable membership identifier. schema: type: string PageSize: name: page_size in: query required: false description: Maximum number of items to return. Default is `50`; maximum is `200`. schema: type: integer minimum: 1 maximum: 200 default: 50 PageToken: name: page_token in: query required: false description: Opaque continuation token returned as `next_page_token` in a previous response. schema: type: string schemas: GameRecord: type: object additionalProperties: false required: - game_id - game_name - game_type - owner_user_id - status - min_players - max_players - start_gap_hours - start_gap_players - enrollment_ends_at - turn_schedule - target_engine_version - created_at - updated_at - current_turn - runtime_status - engine_health_summary properties: game_id: type: string description: Opaque stable game identifier in game-* form. game_name: type: string description: Human-readable game name; mutable in draft status. description: type: string description: Optional game description; mutable in draft and enrollment_open. game_type: type: string enum: - public - private description: Game visibility and enrollment model. owner_user_id: type: string description: Platform user identifier of the private-game owner; empty for public games. status: type: string enum: - draft - enrollment_open - ready_to_start - starting - start_failed - running - paused - finished - cancelled description: Current platform-level lifecycle status. min_players: type: integer description: Minimum approved participants required to proceed to start. max_players: type: integer description: Target roster size that activates the gap window. start_gap_hours: type: integer description: Hours of gap window after max_players is reached. start_gap_players: type: integer description: Additional participants admitted during the gap window. enrollment_ends_at: type: integer format: int64 description: UTC Unix seconds; deadline for automatic enrollment close. turn_schedule: type: string description: Five-field cron expression for scheduled turn generation. target_engine_version: type: string description: Semver of the game engine to launch. created_at: type: integer format: int64 description: UTC Unix milliseconds; record creation timestamp. updated_at: type: integer format: int64 description: UTC Unix milliseconds; last mutation timestamp. started_at: type: integer format: int64 description: UTC Unix milliseconds; set when status becomes running. finished_at: type: integer format: int64 description: UTC Unix milliseconds; set when status becomes finished. current_turn: type: integer description: Denormalized from Game Master; zero until the game is running. runtime_status: type: string description: Denormalized from Game Master; empty until the game is running. engine_health_summary: type: string description: Denormalized from Game Master; empty until the game is running. runtime_binding: $ref: "#/components/schemas/RuntimeBinding" RuntimeBinding: type: object additionalProperties: false description: | Runtime binding metadata produced by Runtime Manager after a successful container start. Set on the game record only after the start sequence succeeds; absent before then. required: - container_id - engine_endpoint - runtime_job_id - bound_at properties: container_id: type: string description: Engine container identifier assigned by Runtime Manager. engine_endpoint: type: string description: Network address Game Master uses to reach the engine container. runtime_job_id: type: string description: | Source `runtime:job_results` Redis Stream message id (in `-` form) that produced this binding. Used for incident investigation. bound_at: type: integer format: int64 description: UTC Unix milliseconds when the binding was persisted. ApplicationRecord: type: object additionalProperties: false required: - application_id - game_id - applicant_user_id - race_name - status - created_at properties: application_id: type: string game_id: type: string applicant_user_id: type: string race_name: type: string status: type: string enum: - submitted - approved - rejected created_at: type: integer format: int64 decided_at: type: integer format: int64 MembershipRecord: type: object additionalProperties: false required: - membership_id - game_id - user_id - race_name - status - joined_at properties: membership_id: type: string game_id: type: string user_id: type: string race_name: type: string status: type: string enum: - active - removed - blocked joined_at: type: integer format: int64 removed_at: type: integer format: int64 CreateGameRequest: type: object additionalProperties: false required: - game_name - game_type - min_players - max_players - start_gap_hours - start_gap_players - enrollment_ends_at - turn_schedule - target_engine_version properties: game_name: type: string description: type: string game_type: type: string enum: - public - private min_players: type: integer minimum: 1 max_players: type: integer minimum: 1 start_gap_hours: type: integer minimum: 0 start_gap_players: type: integer minimum: 0 enrollment_ends_at: type: integer format: int64 turn_schedule: type: string target_engine_version: type: string UpdateGameRequest: type: object additionalProperties: false properties: game_name: type: string description: type: string min_players: type: integer minimum: 1 max_players: type: integer minimum: 1 start_gap_hours: type: integer minimum: 0 start_gap_players: type: integer minimum: 0 enrollment_ends_at: type: integer format: int64 turn_schedule: type: string target_engine_version: type: string GameListResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: "#/components/schemas/GameRecord" next_page_token: type: string MembershipListResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: "#/components/schemas/MembershipRecord" next_page_token: type: string ProbeResponse: type: object additionalProperties: false required: - status properties: status: type: string ErrorResponse: type: object additionalProperties: false required: - error properties: error: $ref: "#/components/schemas/ErrorBody" ErrorBody: type: object additionalProperties: false required: - code - message properties: code: type: string description: Stable internal API error code. message: type: string description: Human-readable trusted error message. responses: InvalidRequestError: description: Request validation failed. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: invalidRequest: value: error: code: invalid_request message: request is invalid NotFoundError: description: The requested game, application, or membership does not exist. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: notFound: value: error: code: subject_not_found message: resource not found ConflictError: description: The requested state transition is not allowed from the current status. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: conflict: value: error: code: conflict message: operation not allowed in current status InternalError: description: Unexpected internal service error. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: internal: value: error: code: internal_error message: internal server error ServiceUnavailableError: description: An upstream dependency is unavailable. content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" examples: unavailable: value: error: code: service_unavailable message: service is unavailable