Files
Ilia Denisov 969c0480ba ui/phase-27: battle viewer (radial scene, playback, map markers)
Engine wire change: Report.battle switched from []uuid.UUID to
[]BattleSummary{id, planet, shots} so the map can place battle
markers without N extra fetches. FBS schema + generated Go/TS
regenerated; transcoder + report controller updated; openapi
adds the BattleSummary schema with a freeze test.

Backend gateway forwards engine GET /api/v1/battle/:turn/:uuid as
/api/v1/user/games/{game_id}/battles/{turn}/{battle_id} (handler
plus engineclient.FetchBattle, contract test stub, openapi spec).

UI:
- BattleViewer (lib/battle-player/) is a logically isolated SVG
  radial scene that consumes a BattleReport prop. Planet at the
  centre, races on the outer ring at equal angular spacing, race
  clusters by (race, className) with <class>:<numLeft> labels;
  observer groups (inBattle: false) are not drawn; eliminated
  races drop out and survivors re-distribute on the next frame.
- Shot line per frame: red on destroyed, green otherwise; erased
  on the next frame. Playback controls: play/pause + step ± +
  rewind + 1x/2x/4x speed (400/200/100 ms per frame).
- Page wrapper (lib/active-view/battle.svelte) loads BattleReport
  via api/battle-fetch.ts; synthetic-gameId prefix routes to a
  fixture loader, otherwise REST through the gateway. Always-
  visible <ol> text protocol satisfies the accessibility ask.
- section-battles.svelte links every battle UUID into the viewer.
- map/battle-markers.ts: yellow X cross of 2 LinePrim through the
  corners of the planet's circumscribed square (stroke width
  clamps from 1 px at 1 shot to 5 px at 100+ shots); bombing
  marker is a stroke-only ring (yellow when damaged, red when
  wiped). Wired into state-binding.ts; click handler dispatches
  battle clicks to the viewer and bombing clicks to the matching
  Reports row.
- i18n keys for the viewer in en + ru.

Docs: ui/docs/battle-viewer-ux.md, FUNCTIONAL.md §6.5 + ru
mirror, ui/PLAN.md Phase 27 decisions + deferred TODOs (push
event, richer class visuals, animated re-distribution).

Tests: Vitest unit (radial layout + timeline frame builder +
marker stroke formula + marker primitives), Playwright e2e for
the viewer (Reports link → viewer, playback step, not-found),
backend engineclient FetchBattle (200 / 404 / bad input), engine
openapi freezes (BattleReport, BattleReportGroup,
BattleActionReport, BattleSummary, Report.battle items).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:24:20 +02:00

3723 lines
111 KiB
YAML

openapi: 3.0.3
info:
title: Galaxy Backend REST API
version: v1
description: |
This specification documents the consolidated `galaxy/backend` REST surface
consumed by `gateway` over the trusted internal network. It covers five
route families:
- `/api/v1/public/*` — unauthenticated public endpoints (only auth in MVP);
- `/healthz`, `/readyz` — unauthenticated infrastructure probes;
- `/api/v1/user/*` — authenticated end-user endpoints; the trusted
`X-User-ID` header injected by gateway is the sole identity input;
- `/api/v1/admin/*` — administrative endpoints gated by HTTP Basic Auth
against the `admin_accounts` table;
- `/api/v1/internal/*` — gateway-only server-to-server endpoints; trusted
as part of the user surface in MVP (no extra auth).
Every endpoint emits a JSON envelope of the shape
`{"error":{"code":"...","message":"..."}}` on failure. The closed set of
`code` values is enumerated under `components.schemas.ErrorBody`. JSON
field names use `snake_case` everywhere on the wire.
servers:
- url: http://backend.internal
description: |
Backend internal listener reachable only from gateway. The actual
address is configured by `BACKEND_HTTP_LISTEN_ADDR`.
tags:
- name: Public
description: Unauthenticated public endpoints (registration and login).
- name: Probes
description: Liveness and readiness probes used by infrastructure tooling.
- name: User
description: Authenticated end-user endpoints; the trusted `X-User-ID` header carries identity.
- name: Admin
description: Administrator endpoints gated by HTTP Basic Auth.
- name: Internal
description: Gateway-only server-to-server endpoints used to lookup and revoke device sessions.
paths:
/healthz:
get:
tags: [Probes]
operationId: getHealthz
summary: Liveness probe
description: Returns `200` for as long as the process is alive.
security: []
responses:
"200":
description: Process is alive.
content:
application/json:
schema:
$ref: "#/components/schemas/HealthzResponse"
"500":
$ref: "#/components/responses/InternalError"
/readyz:
get:
tags: [Probes]
operationId: getReadyz
summary: Readiness probe
description: |
Returns `200` once the Postgres pool is open, embedded migrations are
applied, and the gRPC push listener is bound. Returns `503` until all
of those hold.
security: []
responses:
"200":
description: Process is ready to serve traffic.
content:
application/json:
schema:
$ref: "#/components/schemas/ReadyzResponse"
"503":
$ref: "#/components/responses/ServiceUnavailableError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/public/auth/send-email-code:
post:
tags: [Public]
operationId: publicAuthSendEmailCode
summary: Issue an e-mail login challenge
description: |
Requests a six-digit login code be e-mailed to the supplied address.
Returns the same opaque `challenge_id` shape regardless of whether the
target account exists, so callers cannot use this endpoint to enumerate
user accounts. Permanently blocked addresses are rejected with `400`.
security: []
parameters:
- $ref: "#/components/parameters/AcceptLanguage"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PublicAuthSendEmailCodeRequest"
responses:
"200":
description: Challenge accepted; an e-mail will be delivered out-of-band.
content:
application/json:
schema:
$ref: "#/components/schemas/PublicAuthSendEmailCodeResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/public/auth/confirm-email-code:
post:
tags: [Public]
operationId: publicAuthConfirmEmailCode
summary: Confirm an e-mail login challenge
description: |
Confirms a previously issued `challenge_id` by submitting the delivered
verification `code` together with the client's Ed25519 public key and
IANA time zone. On success the backend creates a device session and
returns its identifier.
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PublicAuthConfirmEmailCodeRequest"
responses:
"200":
description: Device session created.
content:
application/json:
schema:
$ref: "#/components/schemas/PublicAuthConfirmEmailCodeResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/account:
get:
tags: [User]
operationId: userAccountGet
summary: Get the current user account aggregate
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Current account aggregate.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/account/profile:
patch:
tags: [User]
operationId: userAccountUpdateProfile
summary: Update the caller's mutable profile fields
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateProfileRequest"
responses:
"200":
description: Updated account aggregate.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/account/settings:
patch:
tags: [User]
operationId: userAccountUpdateSettings
summary: Update the caller's settings fields
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateSettingsRequest"
responses:
"200":
description: Updated account aggregate.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/account/delete:
post:
tags: [User]
operationId: userAccountDelete
summary: Soft-delete the caller's account
description: |
Marks the caller's account `deleted_at` and triggers the documented
in-process cascade across lobby, notification, and geo modules.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"204":
description: Account scheduled for soft delete.
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games:
get:
tags: [User]
operationId: userLobbyGamesList
summary: List public lobby games with paging
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of public games.
content:
application/json:
schema:
$ref: "#/components/schemas/GameSummaryPage"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
post:
tags: [User]
operationId: userLobbyGamesCreate
summary: Create a new private lobby game owned by the caller
description: |
Always emits a `private` game owned by `X-User-ID`. Public games
are created via `POST /api/v1/admin/games`.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameCreateRequest"
responses:
"201":
description: Game created.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}:
get:
tags: [User]
operationId: userLobbyGamesGet
summary: Get the lobby game detail
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Lobby game detail.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
patch:
tags: [User]
operationId: userLobbyGamesUpdate
summary: Update mutable lobby game fields (owner only)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameUpdateRequest"
responses:
"200":
description: Updated lobby game detail.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/open-enrollment:
post:
tags: [User]
operationId: userLobbyGamesOpenEnrollment
summary: Move a draft game into `enrollment_open`
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Enrollment opened.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/ready-to-start:
post:
tags: [User]
operationId: userLobbyGamesReadyToStart
summary: Mark a game `ready_to_start`
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Game transitioned to `ready_to_start`.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/start:
post:
tags: [User]
operationId: userLobbyGamesStart
summary: Start the engine container for the game
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"202":
description: Start request accepted; runtime job queued.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/pause:
post:
tags: [User]
operationId: userLobbyGamesPause
summary: Pause a running game
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Game paused.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/resume:
post:
tags: [User]
operationId: userLobbyGamesResume
summary: Resume a paused game
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Game resumed.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/cancel:
post:
tags: [User]
operationId: userLobbyGamesCancel
summary: Cancel a game (owner)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Game cancelled.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/retry-start:
post:
tags: [User]
operationId: userLobbyGamesRetryStart
summary: Retry a failed start
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"202":
description: Retry queued.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/applications:
post:
tags: [User]
operationId: userLobbyApplicationsSubmit
summary: Submit an application to join a game
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyApplicationSubmitRequest"
responses:
"201":
description: Application created.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyApplicationDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/applications/{application_id}/approve:
post:
tags: [User]
operationId: userLobbyApplicationsApprove
summary: Approve an application (owner or admin)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/ApplicationID"
responses:
"200":
description: Application approved.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyApplicationDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/applications/{application_id}/reject:
post:
tags: [User]
operationId: userLobbyApplicationsReject
summary: Reject an application (owner or admin)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/ApplicationID"
responses:
"200":
description: Application rejected.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyApplicationDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/invites:
post:
tags: [User]
operationId: userLobbyInvitesIssue
summary: Issue an invite to join a private game
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyInviteIssueRequest"
responses:
"201":
description: Invite created.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyInviteDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/invites/{invite_id}/redeem:
post:
tags: [User]
operationId: userLobbyInvitesRedeem
summary: Redeem an invite to create a membership
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/InviteID"
responses:
"200":
description: Invite redeemed.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyInviteDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/invites/{invite_id}/decline:
post:
tags: [User]
operationId: userLobbyInvitesDecline
summary: Decline an invite (recipient)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/InviteID"
responses:
"200":
description: Invite declined.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyInviteDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/invites/{invite_id}/revoke:
post:
tags: [User]
operationId: userLobbyInvitesRevoke
summary: Revoke an invite (issuer)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/InviteID"
responses:
"200":
description: Invite revoked.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyInviteDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/memberships:
get:
tags: [User]
operationId: userLobbyMembershipsList
summary: List memberships for a game
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Memberships for the game.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyMembershipList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/memberships/{membership_id}/remove:
post:
tags: [User]
operationId: userLobbyMembershipsRemove
summary: Remove a membership (owner or self)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/MembershipID"
responses:
"200":
description: Membership removed.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyMembershipDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/games/{game_id}/memberships/{membership_id}/block:
post:
tags: [User]
operationId: userLobbyMembershipsBlock
summary: Block a membership (owner)
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/MembershipID"
responses:
"200":
description: Membership blocked.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyMembershipDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/my/games:
get:
tags: [User]
operationId: userLobbyMyGames
summary: List games the caller participates in
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Caller's games.
content:
application/json:
schema:
$ref: "#/components/schemas/MyGamesListResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/my/applications:
get:
tags: [User]
operationId: userLobbyMyApplications
summary: List the caller's applications
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Caller's applications.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyApplicationList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/my/invites:
get:
tags: [User]
operationId: userLobbyMyInvites
summary: List the caller's invites
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Caller's invites.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyInviteList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/my/race-names:
get:
tags: [User]
operationId: userLobbyMyRaceNames
summary: List the caller's race names
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Caller's race-name records.
content:
application/json:
schema:
$ref: "#/components/schemas/RaceNameList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/race-names/register:
post:
tags: [User]
operationId: userLobbyRaceNamesRegister
summary: Promote a `pending_registration` to `registered`
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RaceNameRegisterRequest"
responses:
"200":
description: Race name promoted.
content:
application/json:
schema:
$ref: "#/components/schemas/RaceNameDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/commands:
post:
tags: [User]
operationId: userGamesCommands
summary: Forward an engine command batch
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EngineCommand"
responses:
"200":
description: Engine command result passed through.
content:
application/json:
schema:
$ref: "#/components/schemas/PassthroughObject"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/orders:
post:
tags: [User]
operationId: userGamesOrders
summary: Forward an engine order batch
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EngineOrder"
responses:
"200":
description: |
Engine order validation result passed through. Body is the
engine's `UserGamesOrder` shape — game_id, updatedAt, and
the per-command `cmd[]` list with `cmdApplied` /
`cmdErrorCode` populated by the engine.
content:
application/json:
schema:
$ref: "#/components/schemas/PassthroughObject"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
get:
tags: [User]
operationId: userGamesGetOrders
summary: Read the player's stored order for a turn
description: |
Forwards `GET /api/v1/order` against the engine container.
The caller always knows the current turn from the lobby
record at game boot, so `turn` is required.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- name: turn
in: query
required: true
description: Turn number whose stored order to fetch. Non-negative.
schema:
type: integer
format: int32
minimum: 0
responses:
"200":
description: |
Engine returned the stored order for this player + turn.
Body is the engine's `UserGamesOrder` shape.
content:
application/json:
schema:
$ref: "#/components/schemas/PassthroughObject"
"204":
description: No order has been stored for this player on this turn.
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/reports/{turn}:
get:
tags: [User]
operationId: userGamesReport
summary: Read an engine turn report
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/Turn"
responses:
"200":
description: Engine report passed through.
content:
application/json:
schema:
$ref: "#/components/schemas/PassthroughObject"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/battles/{turn}/{battle_id}:
get:
tags: [User]
operationId: userGamesBattle
summary: Read one engine battle report
description: |
Forwards to the engine's `GET /api/v1/battle/:turn/:uuid`. The
engine response body is passed through verbatim. `404 Not Found`
is returned when the battle does not exist for the supplied
`turn` / `battle_id` pair.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/Turn"
- name: battle_id
in: path
required: true
description: Battle identifier (RFC 4122 UUID).
schema:
type: string
format: uuid
responses:
"200":
description: Engine battle report passed through.
content:
application/json:
schema:
$ref: "#/components/schemas/PassthroughObject"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/sessions:
get:
tags: [User]
operationId: userSessionsList
summary: List the caller's active device sessions
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Caller's active device sessions.
content:
application/json:
schema:
$ref: "#/components/schemas/UserSessionList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/sessions/revoke-all:
post:
tags: [User]
operationId: userSessionsRevokeAll
summary: Revoke every device session belonging to the caller
description: |
Logout from every device. Subsequent authenticated requests on
any of the caller's sessions are rejected. Each revocation is
recorded in `session_revocations` with `actor_kind=user_self`.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Caller's sessions revoked.
content:
application/json:
schema:
$ref: "#/components/schemas/DeviceSessionRevocationSummary"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/sessions/{device_session_id}/revoke:
post:
tags: [User]
operationId: userSessionsRevoke
summary: Revoke one of the caller's device sessions
description: |
Logout from a single device. The target `device_session_id`
must belong to the caller; otherwise the endpoint returns
`404 not_found` (the same shape as a missing session) so the
endpoint cannot be used to probe foreign session ids. The
revocation is recorded in `session_revocations` with
`actor_kind=user_self`.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/DeviceSessionID"
responses:
"200":
description: Device session revoked.
content:
application/json:
schema:
$ref: "#/components/schemas/DeviceSession"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/admin-accounts:
get:
tags: [Admin]
operationId: adminAdminAccountsList
summary: List admin accounts
security:
- AdminBasicAuth: []
responses:
"200":
description: Admin accounts.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccountList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
post:
tags: [Admin]
operationId: adminAdminAccountsCreate
summary: Create an admin account
security:
- AdminBasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccountCreateRequest"
responses:
"201":
description: Admin account created.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccount"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/admin-accounts/{username}:
get:
tags: [Admin]
operationId: adminAdminAccountsGet
summary: Get an admin account
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Username"
responses:
"200":
description: Admin account.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccount"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/admin-accounts/{username}/disable:
post:
tags: [Admin]
operationId: adminAdminAccountsDisable
summary: Disable an admin account
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Username"
responses:
"200":
description: Admin account disabled.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccount"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/admin-accounts/{username}/enable:
post:
tags: [Admin]
operationId: adminAdminAccountsEnable
summary: Enable an admin account
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Username"
responses:
"200":
description: Admin account enabled.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccount"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/admin-accounts/{username}/reset-password:
post:
tags: [Admin]
operationId: adminAdminAccountsResetPassword
summary: Reset an admin account password
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Username"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccountResetPasswordRequest"
responses:
"200":
description: Password reset; the new value is delivered out-of-band.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAccount"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/users:
get:
tags: [Admin]
operationId: adminUsersList
summary: List users
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of users.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminUserList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/users/{user_id}:
get:
tags: [Admin]
operationId: adminUsersGet
summary: Get a user account aggregate
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/UserID"
responses:
"200":
description: Account aggregate.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/users/{user_id}/sanctions:
post:
tags: [Admin]
operationId: adminUsersAddSanction
summary: Apply a sanction to a user
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/UserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminUserSanctionRequest"
responses:
"200":
description: Sanction applied.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/users/{user_id}/limits:
post:
tags: [Admin]
operationId: adminUsersAddLimit
summary: Apply a per-user limit override
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/UserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminUserLimitRequest"
responses:
"200":
description: Limit applied.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/users/{user_id}/entitlements:
post:
tags: [Admin]
operationId: adminUsersAddEntitlement
summary: Update a user's entitlement
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/UserID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminUserEntitlementRequest"
responses:
"200":
description: Entitlement updated.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/users/{user_id}/soft-delete:
post:
tags: [Admin]
operationId: adminUsersSoftDelete
summary: Soft-delete a user (admin)
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/UserID"
responses:
"204":
description: User scheduled for soft delete.
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/games:
get:
tags: [Admin]
operationId: adminGamesList
summary: List games for administration
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of games.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminGameList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
post:
tags: [Admin]
operationId: adminGamesCreate
summary: Create a public lobby game (admin-only)
description: |
Creates a public game owned collectively by administrators
(`visibility=public`, `owner_user_id=NULL`). The user-facing
`POST /api/v1/user/lobby/games` only creates private games.
security:
- AdminBasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminGameCreateRequest"
responses:
"201":
description: Public game created.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/games/{game_id}:
get:
tags: [Admin]
operationId: adminGamesGet
summary: Get an admin-side game detail
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Game detail.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameDetail"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/games/{game_id}/force-start:
post:
tags: [Admin]
operationId: adminGamesForceStart
summary: Force-start a game
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
responses:
"202":
description: Force-start queued.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/games/{game_id}/force-stop:
post:
tags: [Admin]
operationId: adminGamesForceStop
summary: Force-stop a game
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Force-stop accepted.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyGameStateChange"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/games/{game_id}/ban-member:
post:
tags: [Admin]
operationId: adminGamesBanMember
summary: Ban a member from a game
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminGameBanMemberRequest"
responses:
"200":
description: Member banned.
content:
application/json:
schema:
$ref: "#/components/schemas/LobbyMembershipDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/runtimes/{game_id}:
get:
tags: [Admin]
operationId: adminRuntimesGet
summary: Read the runtime record for a game
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Runtime record.
content:
application/json:
schema:
$ref: "#/components/schemas/RuntimeRecord"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/runtimes/{game_id}/restart:
post:
tags: [Admin]
operationId: adminRuntimesRestart
summary: Restart the engine container for a game
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
responses:
"202":
description: Restart queued.
content:
application/json:
schema:
$ref: "#/components/schemas/RuntimeOperation"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/runtimes/{game_id}/patch:
post:
tags: [Admin]
operationId: adminRuntimesPatch
summary: Patch the engine version (semver-patch only)
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RuntimePatchRequest"
responses:
"202":
description: Patch queued.
content:
application/json:
schema:
$ref: "#/components/schemas/RuntimeOperation"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/runtimes/{game_id}/force-next-turn:
post:
tags: [Admin]
operationId: adminRuntimesForceNextTurn
summary: Schedule a one-shot extra turn tick
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: One-shot tick scheduled.
content:
application/json:
schema:
$ref: "#/components/schemas/RuntimeOperation"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/engine-versions:
get:
tags: [Admin]
operationId: adminEngineVersionsList
summary: List engine versions
security:
- AdminBasicAuth: []
responses:
"200":
description: Engine versions.
content:
application/json:
schema:
$ref: "#/components/schemas/EngineVersionList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
post:
tags: [Admin]
operationId: adminEngineVersionsCreate
summary: Register a new engine version
security:
- AdminBasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EngineVersionCreateRequest"
responses:
"201":
description: Engine version registered.
content:
application/json:
schema:
$ref: "#/components/schemas/EngineVersion"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/engine-versions/{id}:
patch:
tags: [Admin]
operationId: adminEngineVersionsUpdate
summary: Update an engine version record
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/EngineVersionID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EngineVersionUpdateRequest"
responses:
"200":
description: Engine version updated.
content:
application/json:
schema:
$ref: "#/components/schemas/EngineVersion"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/engine-versions/{id}/disable:
post:
tags: [Admin]
operationId: adminEngineVersionsDisable
summary: Disable an engine version
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/EngineVersionID"
responses:
"200":
description: Engine version disabled.
content:
application/json:
schema:
$ref: "#/components/schemas/EngineVersion"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/deliveries:
get:
tags: [Admin]
operationId: adminMailListDeliveries
summary: List mail deliveries
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of mail deliveries.
content:
application/json:
schema:
$ref: "#/components/schemas/MailDeliveryList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/deliveries/{delivery_id}:
get:
tags: [Admin]
operationId: adminMailGetDelivery
summary: Get a mail delivery
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/DeliveryID"
responses:
"200":
description: Mail delivery.
content:
application/json:
schema:
$ref: "#/components/schemas/MailDelivery"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/deliveries/{delivery_id}/attempts:
get:
tags: [Admin]
operationId: adminMailListDeliveryAttempts
summary: List mail delivery attempts
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/DeliveryID"
responses:
"200":
description: Mail delivery attempts.
content:
application/json:
schema:
$ref: "#/components/schemas/MailAttemptList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/deliveries/{delivery_id}/resend:
post:
tags: [Admin]
operationId: adminMailResendDelivery
summary: Resend a non-sent mail delivery
description: |
Re-arms a delivery for another attempt cycle. Allowed states are
`pending`, `retrying`, and `dead_lettered`. Resend on a `sent`
delivery returns `409 Conflict` to prevent operators from
accidentally dispatching a duplicate copy of an already-delivered
mail.
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/DeliveryID"
responses:
"202":
description: Resend scheduled.
content:
application/json:
schema:
$ref: "#/components/schemas/MailDelivery"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"409":
$ref: "#/components/responses/ConflictError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/dead-letters:
get:
tags: [Admin]
operationId: adminMailListDeadLetters
summary: List mail dead-letters
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of dead-letters.
content:
application/json:
schema:
$ref: "#/components/schemas/MailDeadLetterList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/notifications:
get:
tags: [Admin]
operationId: adminNotificationsList
summary: List notifications
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of notifications.
content:
application/json:
schema:
$ref: "#/components/schemas/NotificationList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/notifications/{notification_id}:
get:
tags: [Admin]
operationId: adminNotificationsGet
summary: Get a notification
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/NotificationID"
responses:
"200":
description: Notification.
content:
application/json:
schema:
$ref: "#/components/schemas/NotificationDetail"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/notifications/dead-letters:
get:
tags: [Admin]
operationId: adminNotificationsListDeadLetters
summary: List notification dead-letters
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of notification dead-letters.
content:
application/json:
schema:
$ref: "#/components/schemas/NotificationDeadLetterList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/notifications/malformed:
get:
tags: [Admin]
operationId: adminNotificationsListMalformed
summary: List malformed notification intents
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/Page"
- $ref: "#/components/parameters/PageSize"
responses:
"200":
description: Page of malformed intents.
content:
application/json:
schema:
$ref: "#/components/schemas/NotificationMalformedList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/geo/users/{user_id}/countries:
get:
tags: [Admin]
operationId: adminGeoListUserCountries
summary: List per-country counters for a user
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/UserID"
responses:
"200":
description: Per-country counters for the user.
content:
application/json:
schema:
$ref: "#/components/schemas/GeoCountryCounterList"
"401":
$ref: "#/components/responses/UnauthorizedError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/internal/sessions/{device_session_id}:
get:
tags: [Internal]
operationId: internalSessionsGet
summary: Look up a device session (gateway-only)
security: []
parameters:
- $ref: "#/components/parameters/DeviceSessionID"
responses:
"200":
description: Device session record.
content:
application/json:
schema:
$ref: "#/components/schemas/DeviceSession"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/internal/users/{user_id}/account-internal:
get:
tags: [Internal]
operationId: internalUsersGetAccountInternal
summary: Server-to-server fetch of an account aggregate (gateway-only)
security: []
parameters:
- $ref: "#/components/parameters/UserID"
responses:
"200":
description: Account aggregate enriched for gateway flows.
content:
application/json:
schema:
$ref: "#/components/schemas/AccountResponse"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
components:
parameters:
AcceptLanguage:
name: Accept-Language
in: header
required: false
description: |
Optional RFC 9110 Accept-Language header forwarded by gateway. Backend
uses it as a fallback locale source when the request body does not
carry an explicit `locale` field.
schema:
type: string
XUserID:
name: X-User-ID
in: header
required: true
description: |
Trusted UUID identifying the calling user. Injected by gateway after
request signature verification. Backend never re-derives identity from
the request body on the user surface.
schema:
type: string
format: uuid
GameID:
name: game_id
in: path
required: true
schema:
type: string
format: uuid
ApplicationID:
name: application_id
in: path
required: true
schema:
type: string
format: uuid
InviteID:
name: invite_id
in: path
required: true
schema:
type: string
format: uuid
MembershipID:
name: membership_id
in: path
required: true
schema:
type: string
format: uuid
NotificationID:
name: notification_id
in: path
required: true
schema:
type: string
format: uuid
DeliveryID:
name: delivery_id
in: path
required: true
schema:
type: string
format: uuid
UserID:
name: user_id
in: path
required: true
schema:
type: string
format: uuid
DeviceSessionID:
name: device_session_id
in: path
required: true
schema:
type: string
format: uuid
EngineVersionID:
name: id
in: path
required: true
schema:
type: string
Username:
name: username
in: path
required: true
schema:
type: string
Turn:
name: turn
in: path
required: true
schema:
type: integer
minimum: 0
Page:
name: page
in: query
required: false
schema:
type: integer
minimum: 1
default: 1
PageSize:
name: page_size
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 50
securitySchemes:
UserHeader:
type: apiKey
in: header
name: X-User-ID
description: |
The trusted UUID forwarded by gateway after the device-session
signature has been verified.
AdminBasicAuth:
type: http
scheme: basic
description: |
Basic Auth credentials checked against `admin_accounts` with bcrypt
cost 12. Failed authentication returns `401` with
`WWW-Authenticate: Basic realm="galaxy-admin"`.
schemas:
HealthzResponse:
type: object
additionalProperties: false
required: [status]
properties:
status:
type: string
enum: [ok]
ReadyzResponse:
type: object
additionalProperties: false
required: [status]
properties:
status:
type: string
enum: [ready, starting]
ErrorBody:
type: object
additionalProperties: false
required: [code, message]
properties:
code:
type: string
description: |
Stable machine-readable failure marker. The closed set is
`not_implemented`, `invalid_request`, `unauthorized`,
`forbidden`, `not_found`, `conflict`, `method_not_allowed`,
`internal_error`, `service_unavailable`,
`turn_already_closed`, `game_paused`.
enum:
- not_implemented
- invalid_request
- unauthorized
- forbidden
- not_found
- conflict
- method_not_allowed
- internal_error
- service_unavailable
- turn_already_closed
- game_paused
message:
type: string
description: Human-readable client-safe failure description.
ErrorResponse:
type: object
additionalProperties: false
required: [error]
properties:
error:
$ref: "#/components/schemas/ErrorBody"
PublicAuthSendEmailCodeRequest:
type: object
additionalProperties: false
required: [email]
properties:
email:
type: string
format: email
locale:
type: string
description: |
Optional BCP 47 locale tag preferred for the delivered code.
Read by the gateway in preference to the request
`Accept-Language` header so Safari clients (which silently
drop JS-set `Accept-Language`) can still pick a non-system
mail language. Empty / malformed values fall back to the
header, which in turn falls back to `en`.
PublicAuthSendEmailCodeResponse:
type: object
additionalProperties: false
required: [challenge_id]
properties:
challenge_id:
type: string
description: Opaque identifier of the issued challenge.
PublicAuthConfirmEmailCodeRequest:
type: object
additionalProperties: false
required: [challenge_id, code, client_public_key, time_zone]
properties:
challenge_id:
type: string
code:
type: string
description: Verification code delivered by mail.
client_public_key:
type: string
description: Standard base64-encoded raw 32-byte Ed25519 public key.
time_zone:
type: string
description: IANA time-zone identifier provided by the client.
PublicAuthConfirmEmailCodeResponse:
type: object
additionalProperties: false
required: [device_session_id]
properties:
device_session_id:
type: string
format: uuid
ActorRef:
type: object
additionalProperties: false
required: [type]
properties:
type:
type: string
id:
type: string
EntitlementSnapshot:
type: object
additionalProperties: false
required:
- plan_code
- is_paid
- source
- actor
- reason_code
- starts_at
- max_registered_race_names
- updated_at
properties:
plan_code:
type: string
enum: [free, monthly, yearly, permanent]
description: |
Closed tier vocabulary. The wire field name is `plan_code`;
the storage column is `entitlement_snapshots.tier`.
is_paid:
type: boolean
source:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
reason_code:
type: string
starts_at:
type: string
format: date-time
ends_at:
type: string
format: date-time
nullable: true
max_registered_race_names:
type: integer
description: |
Derived from the tier policy table. `free` accounts get 1;
`monthly`, `yearly`, and `permanent` accounts get 5 in MVP.
updated_at:
type: string
format: date-time
ActiveSanction:
type: object
additionalProperties: false
required: [sanction_code, scope, reason_code, actor, applied_at]
properties:
sanction_code:
type: string
scope:
type: string
reason_code:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
applied_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
nullable: true
ActiveLimit:
type: object
additionalProperties: false
required: [limit_code, value, reason_code, actor, applied_at]
properties:
limit_code:
type: string
value:
type: integer
reason_code:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
applied_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
nullable: true
Account:
type: object
additionalProperties: false
required:
- user_id
- email
- user_name
- preferred_language
- time_zone
- entitlement
- active_sanctions
- active_limits
- created_at
- updated_at
properties:
user_id:
type: string
format: uuid
email:
type: string
format: email
user_name:
type: string
display_name:
type: string
preferred_language:
type: string
time_zone:
type: string
declared_country:
type: string
entitlement:
$ref: "#/components/schemas/EntitlementSnapshot"
active_sanctions:
type: array
items:
$ref: "#/components/schemas/ActiveSanction"
active_limits:
type: array
items:
$ref: "#/components/schemas/ActiveLimit"
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
AccountResponse:
type: object
additionalProperties: false
required: [account]
properties:
account:
$ref: "#/components/schemas/Account"
UpdateProfileRequest:
type: object
additionalProperties: false
properties:
display_name:
type: string
description: Replacement display name; an empty value clears the field.
UpdateSettingsRequest:
type: object
additionalProperties: false
properties:
preferred_language:
type: string
time_zone:
type: string
GameSummary:
type: object
additionalProperties: false
required:
- game_id
- game_name
- game_type
- status
- min_players
- max_players
- enrollment_ends_at
- created_at
- updated_at
- current_turn
properties:
game_id:
type: string
format: uuid
game_name:
type: string
game_type:
type: string
enum: [public, private]
description: |
Wire alias for `visibility`; values match the storage column
`games.visibility`. `public` games are admin-created and
carry `owner_user_id IS NULL`; `private` games are owned by
the calling user.
status:
type: string
enum:
- draft
- enrollment_open
- ready_to_start
- starting
- start_failed
- running
- paused
- finished
- cancelled
owner_user_id:
type: string
format: uuid
nullable: true
description: |
Owner user_id for private games; `null` for public games whose
ownership is collective and managed by administrators.
min_players:
type: integer
minimum: 1
max_players:
type: integer
minimum: 1
enrollment_ends_at:
type: string
format: date-time
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
current_turn:
type: integer
description: |
Most recent turn number observed by backend's runtime
projection. Zero before the engine produces its first
snapshot. The user surface uses it to fetch the matching
`user.games.report` without a separate state query.
GameSummaryPage:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/GameSummary"
page:
type: integer
page_size:
type: integer
total:
type: integer
MyGamesListResponse:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/GameSummary"
LobbyGameCreateRequest:
type: object
additionalProperties: false
required:
- game_name
- visibility
- min_players
- max_players
- start_gap_hours
- start_gap_players
- enrollment_ends_at
- turn_schedule
- target_engine_version
properties:
game_name:
type: string
minLength: 1
visibility:
type: string
enum: [private]
description: |
User-facing game creation always emits `private` games. Public
games are created by admins via `POST /api/v1/admin/games`.
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: string
format: date-time
turn_schedule:
type: string
description: Five-field cron expression accepted by `pkg/cronutil.Parse`.
target_engine_version:
type: string
description: |
Engine version label (semver). Cross-checked against
`engine_versions` at start time; rejected if no enabled row
matches.
LobbyGameUpdateRequest:
type: object
additionalProperties: false
description: |
Mutable lobby game fields (owner-only patch). Status transitions are
driven through dedicated endpoints (`open-enrollment`,
`ready-to-start`, `start`, `pause`, `resume`, `cancel`,
`retry-start`).
properties:
game_name:
type: string
minLength: 1
description:
type: string
enrollment_ends_at:
type: string
format: date-time
turn_schedule:
type: string
target_engine_version:
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
AdminGameCreateRequest:
type: object
additionalProperties: false
required:
- game_name
- min_players
- max_players
- start_gap_hours
- start_gap_players
- enrollment_ends_at
- turn_schedule
- target_engine_version
description: |
Admin-side public-game creation. The `visibility` of the created
record is hard-coded to `public` and `owner_user_id` is `NULL`.
properties:
game_name:
type: string
minLength: 1
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: string
format: date-time
turn_schedule:
type: string
target_engine_version:
type: string
LobbyGameDetail:
allOf:
- $ref: "#/components/schemas/GameSummary"
- type: object
additionalProperties: false
required:
- visibility
- turn_schedule
- target_engine_version
- start_gap_hours
- start_gap_players
- runtime_status
properties:
visibility:
type: string
enum: [public, private]
description:
type: string
turn_schedule:
type: string
target_engine_version:
type: string
start_gap_hours:
type: integer
start_gap_players:
type: integer
runtime_status:
type: string
engine_health:
type: string
started_at:
type: string
format: date-time
nullable: true
finished_at:
type: string
format: date-time
nullable: true
LobbyGameStateChange:
type: object
additionalProperties: false
required: [game_id, status]
properties:
game_id:
type: string
format: uuid
status:
type: string
runtime_status:
type: string
LobbyApplicationSubmitRequest:
type: object
additionalProperties: false
required: [race_name]
properties:
race_name:
type: string
minLength: 1
LobbyApplicationDetail:
type: object
additionalProperties: false
required:
- application_id
- game_id
- applicant_user_id
- race_name
- status
- created_at
properties:
application_id:
type: string
format: uuid
game_id:
type: string
format: uuid
applicant_user_id:
type: string
format: uuid
race_name:
type: string
status:
type: string
enum: [pending, approved, rejected]
created_at:
type: string
format: date-time
decided_at:
type: string
format: date-time
nullable: true
LobbyApplicationList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/LobbyApplicationDetail"
LobbyInviteIssueRequest:
type: object
additionalProperties: false
description: |
Issues either a user-bound invite (when `invited_user_id` is set)
or a one-shot code-based invite (when omitted). The server
generates the redemption `code` for the latter.
properties:
invited_user_id:
type: string
format: uuid
race_name:
type: string
minLength: 1
expires_at:
type: string
format: date-time
LobbyInviteDetail:
type: object
additionalProperties: false
required:
- invite_id
- game_id
- inviter_user_id
- status
- race_name
- created_at
- expires_at
properties:
invite_id:
type: string
format: uuid
game_id:
type: string
format: uuid
inviter_user_id:
type: string
format: uuid
invited_user_id:
type: string
format: uuid
nullable: true
code:
type: string
nullable: true
race_name:
type: string
status:
type: string
enum: [pending, redeemed, declined, revoked, expired]
created_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
decided_at:
type: string
format: date-time
nullable: true
LobbyInviteList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/LobbyInviteDetail"
LobbyMembershipDetail:
type: object
additionalProperties: false
required:
- membership_id
- game_id
- user_id
- race_name
- canonical_key
- status
- joined_at
properties:
membership_id:
type: string
format: uuid
game_id:
type: string
format: uuid
user_id:
type: string
format: uuid
race_name:
type: string
canonical_key:
type: string
status:
type: string
enum: [active, removed, blocked]
joined_at:
type: string
format: date-time
removed_at:
type: string
format: date-time
nullable: true
LobbyMembershipList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/LobbyMembershipDetail"
RaceNameDetail:
type: object
additionalProperties: false
required: [name, canonical, status, owner_user_id]
properties:
name:
type: string
canonical:
type: string
status:
type: string
enum: [registered, reservation, pending_registration]
owner_user_id:
type: string
format: uuid
game_id:
type: string
format: uuid
nullable: true
source_game_id:
type: string
format: uuid
nullable: true
reserved_at:
type: string
format: date-time
nullable: true
expires_at:
type: string
format: date-time
nullable: true
registered_at:
type: string
format: date-time
nullable: true
RaceNameList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/RaceNameDetail"
RaceNameRegisterRequest:
type: object
additionalProperties: false
required: [name]
properties:
name:
type: string
EngineCommand:
type: object
additionalProperties: true
description: |
Engine command request body. The schema is permissive because the
engine proxy passes the body through verbatim; the typed shape
lives in `pkg/model/rest.Command` and is enforced by
`internal/engineclient` before the engine call leaves backend.
EngineOrder:
type: object
additionalProperties: true
description: |
Engine order request body. Permissive on the wire; typed shape
lives in `pkg/model/order.Order`.
PassthroughObject:
type: object
additionalProperties: true
description: |
Permissive placeholder used for engine pass-through responses
(`pkg/model/{rest,report}` types are the authoritative shape).
AdminAccount:
type: object
additionalProperties: false
required: [username, created_at]
properties:
username:
type: string
created_at:
type: string
format: date-time
last_used_at:
type: string
format: date-time
nullable: true
disabled_at:
type: string
format: date-time
nullable: true
AdminAccountList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/AdminAccount"
AdminAccountCreateRequest:
type: object
additionalProperties: false
required: [username, password]
properties:
username:
type: string
password:
type: string
format: password
AdminAccountResetPasswordRequest:
type: object
additionalProperties: false
required: [password]
properties:
password:
type: string
format: password
AdminUserList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/Account"
page:
type: integer
page_size:
type: integer
total:
type: integer
AdminUserSanctionRequest:
type: object
additionalProperties: false
required: [sanction_code, scope, reason_code, actor]
properties:
sanction_code:
type: string
enum: [permanent_block]
description: |
Closed MVP set; only `permanent_block` is supported. Applying
it triggers the in-process cascade (revoke all sessions,
release lobby memberships and Race Name Directory entries).
scope:
type: string
reason_code:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
expires_at:
type: string
format: date-time
nullable: true
AdminUserLimitRequest:
type: object
additionalProperties: false
required: [limit_code, value, reason_code, actor]
properties:
limit_code:
type: string
value:
type: integer
reason_code:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
expires_at:
type: string
format: date-time
nullable: true
AdminUserEntitlementRequest:
type: object
additionalProperties: false
required: [tier, source, actor]
properties:
tier:
type: string
enum: [free, monthly, yearly, permanent]
source:
type: string
actor:
$ref: "#/components/schemas/ActorRef"
reason_code:
type: string
starts_at:
type: string
format: date-time
ends_at:
type: string
format: date-time
nullable: true
AdminGameList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/LobbyGameDetail"
page:
type: integer
page_size:
type: integer
total:
type: integer
AdminGameBanMemberRequest:
type: object
additionalProperties: false
required: [user_id, reason]
properties:
user_id:
type: string
format: uuid
reason:
type: string
minLength: 1
RuntimeRecord:
type: object
additionalProperties: true
required: [game_id, status]
properties:
game_id:
type: string
format: uuid
status:
type: string
current_container_id:
type: string
image_ref:
type: string
started_at:
type: string
format: date-time
nullable: true
last_observed_at:
type: string
format: date-time
nullable: true
RuntimeOperation:
type: object
additionalProperties: true
required: [operation_id, game_id, op, status, started_at]
properties:
operation_id:
type: string
format: uuid
game_id:
type: string
format: uuid
op:
type: string
status:
type: string
started_at:
type: string
format: date-time
finished_at:
type: string
format: date-time
nullable: true
error:
type: string
RuntimePatchRequest:
type: object
additionalProperties: false
required: [target_version]
properties:
target_version:
type: string
description: Semver-patch target inside the same major/minor line.
EngineVersion:
type: object
additionalProperties: false
required: [version, image_ref, enabled, created_at]
properties:
version:
type: string
image_ref:
type: string
enabled:
type: boolean
created_at:
type: string
format: date-time
EngineVersionList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/EngineVersion"
EngineVersionCreateRequest:
type: object
additionalProperties: false
required: [version, image_ref]
properties:
version:
type: string
image_ref:
type: string
enabled:
type: boolean
EngineVersionUpdateRequest:
type: object
additionalProperties: false
properties:
image_ref:
type: string
enabled:
type: boolean
MailDelivery:
type: object
additionalProperties: true
required: [delivery_id, template_id, status, attempts, created_at]
properties:
delivery_id:
type: string
format: uuid
template_id:
type: string
idempotency_key:
type: string
status:
type: string
attempts:
type: integer
next_attempt_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
MailDeliveryList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/MailDelivery"
page:
type: integer
page_size:
type: integer
total:
type: integer
MailAttempt:
type: object
additionalProperties: true
required: [attempt_id, delivery_id, attempt_no, started_at]
properties:
attempt_id:
type: string
format: uuid
delivery_id:
type: string
format: uuid
attempt_no:
type: integer
started_at:
type: string
format: date-time
finished_at:
type: string
format: date-time
nullable: true
outcome:
type: string
error:
type: string
MailAttemptList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/MailAttempt"
MailDeadLetter:
type: object
additionalProperties: true
required: [dead_letter_id, delivery_id, archived_at]
properties:
dead_letter_id:
type: string
format: uuid
delivery_id:
type: string
format: uuid
archived_at:
type: string
format: date-time
reason:
type: string
MailDeadLetterList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/MailDeadLetter"
page:
type: integer
page_size:
type: integer
total:
type: integer
NotificationDetail:
type: object
additionalProperties: true
required: [notification_id, kind, idempotency_key, created_at]
properties:
notification_id:
type: string
format: uuid
kind:
type: string
idempotency_key:
type: string
user_id:
type: string
format: uuid
payload:
type: object
additionalProperties: true
created_at:
type: string
format: date-time
NotificationList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/NotificationDetail"
page:
type: integer
page_size:
type: integer
total:
type: integer
NotificationDeadLetter:
type: object
additionalProperties: true
required: [dead_letter_id, notification_id, archived_at]
properties:
dead_letter_id:
type: string
format: uuid
notification_id:
type: string
format: uuid
archived_at:
type: string
format: date-time
reason:
type: string
NotificationDeadLetterList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/NotificationDeadLetter"
page:
type: integer
page_size:
type: integer
total:
type: integer
NotificationMalformed:
type: object
additionalProperties: true
required: [id, received_at]
properties:
id:
type: string
format: uuid
received_at:
type: string
format: date-time
payload:
type: object
additionalProperties: true
reason:
type: string
NotificationMalformedList:
type: object
additionalProperties: false
required: [items, page, page_size, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/NotificationMalformed"
page:
type: integer
page_size:
type: integer
total:
type: integer
GeoCountryCounter:
type: object
additionalProperties: false
required: [country, count]
properties:
country:
type: string
description: ISO 3166-1 alpha-2 country code.
count:
type: integer
last_seen_at:
type: string
format: date-time
nullable: true
GeoCountryCounterList:
type: object
additionalProperties: false
required: [user_id, items]
properties:
user_id:
type: string
format: uuid
items:
type: array
items:
$ref: "#/components/schemas/GeoCountryCounter"
DeviceSession:
type: object
additionalProperties: false
required: [device_session_id, user_id, status, created_at]
properties:
device_session_id:
type: string
format: uuid
user_id:
type: string
format: uuid
status:
type: string
client_public_key:
type: string
description: Standard base64-encoded raw 32-byte Ed25519 public key.
created_at:
type: string
format: date-time
revoked_at:
type: string
format: date-time
nullable: true
last_seen_at:
type: string
format: date-time
nullable: true
DeviceSessionRevocationSummary:
type: object
additionalProperties: false
required: [user_id, revoked_count]
properties:
user_id:
type: string
format: uuid
revoked_count:
type: integer
UserSessionList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/DeviceSession"
responses:
NotImplementedError:
description: Endpoint is documented but not implemented yet.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
placeholder:
value:
error:
code: not_implemented
message: endpoint is not implemented yet
InvalidRequestError:
description: Request body or field values are invalid.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
invalidRequest:
value:
error:
code: invalid_request
message: request payload is invalid
UnauthorizedError:
description: Basic authentication credentials are missing or rejected.
headers:
WWW-Authenticate:
description: Basic challenge advertised on rejected admin requests.
schema:
type: string
example: Basic realm="galaxy-admin"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
unauthorized:
value:
error:
code: unauthorized
message: basic authentication is required
ForbiddenError:
description: Caller is authenticated but not allowed to perform the action.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
forbidden:
value:
error:
code: forbidden
message: caller is not authorized for this action
NotFoundError:
description: The requested resource was not found.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
notFound:
value:
error:
code: not_found
message: resource was not found
ConflictError:
description: The request conflicts with the current state of the target resource.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
conflict:
value:
error:
code: conflict
message: resource already exists
MethodNotAllowedError:
description: Request method is not allowed for the target route.
headers:
Allow:
description: Comma-separated list of accepted methods.
schema:
type: string
example: GET
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
methodNotAllowed:
value:
error:
code: method_not_allowed
message: request method is not allowed for this route
InternalError:
description: Internal backend error while processing the request.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
internalError:
value:
error:
code: internal_error
message: internal server error
ServiceUnavailableError:
description: Backend is starting up or temporarily cannot serve the request.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
examples:
unavailable:
value:
error:
code: service_unavailable
message: backend is not ready