Files
galaxy-game/backend/openapi.yaml
T
Ilia Denisov 7b43ce5844
Tests · Go / test (push) Successful in 1m56s
Tests · Integration / integration (pull_request) Successful in 1m47s
Tests · Go / test (pull_request) Successful in 2m2s
Phase 28 (Step 1): backend support for race-name mail send
Phase 28's in-game mail UI groups personal threads by the other
party's race. To support that without an extra membership-listing
RPC, the diplomail subsystem now:

- accepts `recipient_race_name` on `POST /messages` and
  `POST /admin` (target=user) as an alternative to
  `recipient_user_id`; the service resolves it via the existing
  `Memberships.ListMembers(gameID, "active")` and rejects with
  `forbidden` when the matching member is no longer active;
- snapshots `diplomail_messages.sender_race_name` at send time for
  every player sender (admin / system rows stay NULL). The UI keys
  per-race threading on this column.

Schema, openapi, README, and a focused e2e test for the new path
(happy path + dual / missing / unknown / kicked errors) land in
this commit; the gateway + UI legs follow in subsequent commits on
this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 22:07:48 +02:00

4618 lines
139 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/games/{game_id}/mail/messages:
post:
tags: [User]
operationId: userMailSendPersonal
summary: Send a personal diplomatic mail message
description: |
Sends a replyable personal message from the authenticated user
to another active member of the same game. Both sender and
recipient must be active members. Body is plain UTF-8 text
(no HTML processing on the server); `subject` is optional.
Body length is capped at `BACKEND_DIPLOMAIL_MAX_BODY_BYTES`
(default 4096) and subject length at
`BACKEND_DIPLOMAIL_MAX_SUBJECT_BYTES` (default 256).
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailSendRequest"
responses:
"201":
description: Personal message accepted and persisted.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailMessageDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/mail/broadcast:
post:
tags: [User]
operationId: userMailSendBroadcast
summary: Send a paid-tier personal broadcast to a game's active members
description: |
Paid-tier players (`entitlement.is_paid == true`) may send one
personal message that fans out to every other active member of
the game. Free-tier callers receive 403. The resulting rows
carry `kind="personal"`, `sender_kind="player"`,
`broadcast_scope="game_broadcast"`. Recipients reply through
the regular personal-send endpoint; the reply targets the
broadcaster only.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailSendBroadcastRequest"
responses:
"201":
description: Personal broadcast accepted; receipt carries the recipient count.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailBroadcastReceipt"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/mail/admin:
post:
tags: [User]
operationId: userMailSendAdmin
summary: Send a non-replyable admin notification (owner only)
description: |
Owner-only: the caller must be the owner of the private game.
`target="user"` requires `recipient_user_id`; `target="all"`
accepts an optional `recipients` scope (`active` by default,
plus `active_and_removed` and `all_members`). The message
carries `kind="admin"` and is therefore non-replyable.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailSendAdminRequest"
responses:
"201":
description: Admin message persisted; broadcasts return a fan-out receipt.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/UserMailMessageDetail"
- $ref: "#/components/schemas/UserMailBroadcastReceipt"
"400":
$ref: "#/components/responses/InvalidRequestError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/mail/messages/{message_id}:
get:
tags: [User]
operationId: userMailGet
summary: Read one diplomatic mail message
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/MessageID"
responses:
"200":
description: Message addressed to the caller.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailMessageDetail"
"400":
$ref: "#/components/responses/InvalidRequestError"
"404":
$ref: "#/components/responses/NotFoundError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
delete:
tags: [User]
operationId: userMailDelete
summary: Soft-delete a previously-read message
description: |
Marks the caller's recipient row for the message as deleted.
The underlying message stays persisted (admin / system mail is
retained for the lifetime of the game). The recipient row must
have `read_at` set first; otherwise the call returns 409.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/MessageID"
responses:
"200":
description: Message soft-deleted for the caller.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailRecipientState"
"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}/mail/messages/{message_id}/read:
post:
tags: [User]
operationId: userMailMarkRead
summary: Mark a diplomatic mail message as read
description: |
Idempotent. Sets `read_at` on the caller's recipient row when
it is still unread; a second call on an already-read row is a
no-op and the existing state is returned.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
- $ref: "#/components/parameters/MessageID"
responses:
"200":
description: Recipient state after the mark-read.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailRecipientState"
"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}/mail/inbox:
get:
tags: [User]
operationId: userMailInbox
summary: List the caller's inbox for a game
description: |
Returns every non-soft-deleted mail row addressed to the
caller in the given game, newest first. Includes the
per-recipient read state. Soft access: the caller may not be
an active member if every visible row carries
`kind="admin"`.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Inbox entries for the caller in the given game.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailInboxList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/games/{game_id}/mail/sent:
get:
tags: [User]
operationId: userMailSent
summary: List the caller's sent personal messages in a game
description: |
Returns personal messages authored by the caller in the given
game, newest first. Admin / system messages are not listed
(they have no `sender_user_id`).
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
- $ref: "#/components/parameters/GameID"
responses:
"200":
description: Sent personal messages by the caller.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailSentList"
"400":
$ref: "#/components/responses/InvalidRequestError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/user/lobby/mail/unread-counts:
get:
tags: [User]
operationId: userMailUnreadCounts
summary: Per-game and total unread mail counts for the caller
description: |
Drives the lobby badge: returns one entry per game the caller
has any unread mail in, plus the global total. The response
is empty (and `total == 0`) when there is nothing unread.
security:
- UserHeader: []
parameters:
- $ref: "#/components/parameters/XUserID"
responses:
"200":
description: Per-game unread counts addressed to the caller.
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailUnreadCountsResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"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/mail/broadcast:
post:
tags: [Admin]
operationId: adminDiplomailBroadcast
summary: Multi-game admin broadcast
description: |
Fans out one admin-kind broadcast across the games selected
by `scope`. `scope="selected"` requires `game_ids`;
`scope="all_running"` enumerates every game whose status is
non-terminal. Recipients are resolved per-game via the same
scope vocabulary as the per-game admin send. A recipient
appearing in multiple addressed games receives one
independently-deletable inbox entry per game.
security:
- AdminBasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminDiplomailBroadcastRequest"
responses:
"201":
description: Broadcast accepted; per-game message ids and total recipient count.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminDiplomailBroadcastResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/cleanup:
post:
tags: [Admin]
operationId: adminDiplomailCleanup
summary: Bulk-purge diplomail messages from old finished games
description: |
Removes every `diplomail_messages` row whose game finished
more than `older_than_years` years ago. Cascading FKs prune
the recipient and translation tables in the same transaction.
`older_than_years` must be >= 1.
security:
- AdminBasicAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AdminDiplomailCleanupRequest"
responses:
"200":
description: Cleanup result.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminDiplomailCleanupResponse"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"501":
$ref: "#/components/responses/NotImplementedError"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/admin/mail/messages:
get:
tags: [Admin]
operationId: adminDiplomailList
summary: Paginated admin view of diplomail messages
description: |
Returns the canonical message rows for admin observability.
Optional filters: `game_id`, `kind` (personal / admin),
`sender_kind` (player / admin / system). Pagination via
`page` and `page_size`.
security:
- AdminBasicAuth: []
parameters:
- name: page
in: query
required: false
schema:
type: integer
minimum: 1
- name: page_size
in: query
required: false
schema:
type: integer
minimum: 1
- name: game_id
in: query
required: false
schema:
type: string
format: uuid
- name: kind
in: query
required: false
schema:
type: string
enum: [personal, admin]
- name: sender_kind
in: query
required: false
schema:
type: string
enum: [player, admin, system]
responses:
"200":
description: Paginated diplomail messages.
content:
application/json:
schema:
$ref: "#/components/schemas/AdminDiplomailListResponse"
"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}/mail:
post:
tags: [Admin]
operationId: adminDiplomailSend
summary: Send a diplomatic-mail admin notification to one game
description: |
Site-admin send for the diplomatic-mail subsystem. Body shape
mirrors the owner-only `POST /api/v1/user/games/{game_id}/mail/admin`
endpoint. `target="user"` requires `recipient_user_id`;
`target="all"` accepts an optional `recipients` scope
(`active` / `active_and_removed` / `all_members`). The
authenticated admin username is persisted as `sender_username`.
security:
- AdminBasicAuth: []
parameters:
- $ref: "#/components/parameters/GameID"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserMailSendAdminRequest"
responses:
"201":
description: Admin message persisted; broadcasts return a fan-out receipt.
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/UserMailMessageDetail"
- $ref: "#/components/schemas/UserMailBroadcastReceipt"
"400":
$ref: "#/components/responses/InvalidRequestError"
"401":
$ref: "#/components/responses/UnauthorizedError"
"403":
$ref: "#/components/responses/ForbiddenError"
"404":
$ref: "#/components/responses/NotFoundError"
"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
MessageID:
name: message_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"
UserMailSendRequest:
type: object
additionalProperties: false
required: [body]
properties:
recipient_user_id:
type: string
format: uuid
description: |
Either `recipient_user_id` or `recipient_race_name` must
be supplied; supplying both is rejected as
`invalid_request`.
recipient_race_name:
type: string
description: |
Resolves to the active member with this race name in the
game. Mutually exclusive with `recipient_user_id`. The
server returns `forbidden` when the matching member is no
longer active (lobby-removed / blocked).
subject:
type: string
description: |
Optional subject. Empty string and missing field are
treated the same.
body:
type: string
description: Plain UTF-8 body. HTML is not parsed on the server.
UserMailSendAdminRequest:
type: object
additionalProperties: false
required: [target, body]
properties:
target:
type: string
enum: [user, all]
recipient_user_id:
type: string
format: uuid
description: |
One of `recipient_user_id` and `recipient_race_name` is
required when `target="user"`. Identifies the recipient
of the personal admin message by uuid; the recipient may
be in any membership status (admin notifications can
reach kicked players when addressed by user_id).
recipient_race_name:
type: string
description: |
Optional alternative to `recipient_user_id` when
`target="user"`. Resolves to the active member with this
race name in the game; lobby-removed and blocked members
cannot be reached through the race-name shortcut.
recipients:
type: string
enum: [active, active_and_removed, all_members]
description: |
Optional scope when `target="all"`. Defaults to `active`.
subject:
type: string
body:
type: string
UserMailBroadcastReceipt:
type: object
additionalProperties: false
required:
- message_id
- game_id
- kind
- sender_kind
- body
- body_lang
- broadcast_scope
- created_at
- recipient_count
properties:
message_id:
type: string
format: uuid
game_id:
type: string
format: uuid
game_name:
type: string
kind:
type: string
enum: [personal, admin]
sender_kind:
type: string
enum: [player, admin, system]
subject:
type: string
body:
type: string
body_lang:
type: string
broadcast_scope:
type: string
enum: [single, game_broadcast, multi_game_broadcast]
created_at:
type: string
format: date-time
recipient_count:
type: integer
minimum: 0
UserMailSendBroadcastRequest:
type: object
additionalProperties: false
required: [body]
properties:
subject:
type: string
body:
type: string
AdminDiplomailBroadcastRequest:
type: object
additionalProperties: false
required: [scope, body]
properties:
scope:
type: string
enum: [selected, all_running]
game_ids:
type: array
items:
type: string
format: uuid
recipients:
type: string
enum: [active, active_and_removed, all_members]
subject:
type: string
body:
type: string
AdminDiplomailBroadcastResponse:
type: object
additionalProperties: false
required: [recipient_count, messages]
properties:
recipient_count:
type: integer
minimum: 0
messages:
type: array
items:
type: object
additionalProperties: false
required: [message_id, game_id]
properties:
message_id:
type: string
format: uuid
game_id:
type: string
format: uuid
game_name:
type: string
AdminDiplomailCleanupRequest:
type: object
additionalProperties: false
required: [older_than_years]
properties:
older_than_years:
type: integer
minimum: 1
AdminDiplomailCleanupResponse:
type: object
additionalProperties: false
required: [messages_deleted, game_ids]
properties:
messages_deleted:
type: integer
minimum: 0
game_ids:
type: array
items:
type: string
format: uuid
AdminDiplomailMessage:
type: object
additionalProperties: false
required:
- message_id
- game_id
- kind
- sender_kind
- body
- body_lang
- broadcast_scope
- created_at
properties:
message_id:
type: string
format: uuid
game_id:
type: string
format: uuid
game_name:
type: string
kind:
type: string
enum: [personal, admin]
sender_kind:
type: string
enum: [player, admin, system]
sender_user_id:
type: string
format: uuid
nullable: true
sender_username:
type: string
nullable: true
sender_ip:
type: string
subject:
type: string
body:
type: string
body_lang:
type: string
broadcast_scope:
type: string
enum: [single, game_broadcast, multi_game_broadcast]
created_at:
type: string
format: date-time
AdminDiplomailListResponse:
type: object
additionalProperties: false
required: [total, page, page_size, items]
properties:
total:
type: integer
minimum: 0
page:
type: integer
minimum: 1
page_size:
type: integer
minimum: 1
items:
type: array
items:
$ref: "#/components/schemas/AdminDiplomailMessage"
UserMailMessageDetail:
type: object
additionalProperties: false
required:
- message_id
- game_id
- kind
- sender_kind
- body
- body_lang
- broadcast_scope
- created_at
- recipient_user_id
properties:
message_id:
type: string
format: uuid
game_id:
type: string
format: uuid
game_name:
type: string
kind:
type: string
enum: [personal, admin]
sender_kind:
type: string
enum: [player, admin, system]
sender_user_id:
type: string
format: uuid
nullable: true
sender_username:
type: string
nullable: true
sender_race_name:
type: string
nullable: true
description: |
Snapshot of the sender's race name in this game at send
time. Populated when `sender_kind="player"` and the
sender had an active membership at send time; nil for
admin and system messages, and for player messages sent
by a private-game owner who was not an active member at
send time. The in-game UI keys per-race threading on this
field.
subject:
type: string
body:
type: string
body_lang:
type: string
description: BCP 47 tag. `und` until Stage D adds detection.
broadcast_scope:
type: string
enum: [single, game_broadcast, multi_game_broadcast]
created_at:
type: string
format: date-time
recipient_user_id:
type: string
format: uuid
recipient_user_name:
type: string
recipient_race_name:
type: string
nullable: true
read_at:
type: string
format: date-time
nullable: true
deleted_at:
type: string
format: date-time
nullable: true
translated_subject:
type: string
description: |
Subject rendered into the caller's preferred_language by
the translation cache. Absent when the caller's language
matches `body_lang` or the translator could not produce
a rendering.
translated_body:
type: string
description: |
Body rendered into the caller's preferred_language. Same
absence semantics as `translated_subject`.
translation_lang:
type: string
description: BCP 47 tag of the rendered translation.
translator:
type: string
description: Identifier of the translation engine that produced the cached row.
UserMailSentSummary:
type: object
additionalProperties: false
required:
- message_id
- game_id
- kind
- body
- body_lang
- broadcast_scope
- created_at
properties:
message_id:
type: string
format: uuid
game_id:
type: string
format: uuid
game_name:
type: string
kind:
type: string
enum: [personal, admin]
subject:
type: string
body:
type: string
body_lang:
type: string
broadcast_scope:
type: string
enum: [single, game_broadcast, multi_game_broadcast]
created_at:
type: string
format: date-time
UserMailInboxList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/UserMailMessageDetail"
UserMailSentList:
type: object
additionalProperties: false
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/UserMailSentSummary"
UserMailUnreadCount:
type: object
additionalProperties: false
required: [game_id, unread]
properties:
game_id:
type: string
format: uuid
game_name:
type: string
unread:
type: integer
minimum: 0
UserMailUnreadCountsResponse:
type: object
additionalProperties: false
required: [total, items]
properties:
total:
type: integer
minimum: 0
items:
type: array
items:
$ref: "#/components/schemas/UserMailUnreadCount"
UserMailRecipientState:
type: object
additionalProperties: false
required: [message_id]
properties:
message_id:
type: string
format: uuid
read_at:
type: string
format: date-time
nullable: true
deleted_at:
type: string
format: date-time
nullable: true
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