57d2286f5e
Phase 28's in-game mail UI threads sent messages by the recipient race name, so the bulk `/sent` endpoint now returns the same `UserMailMessageDetail` shape as `/inbox` — single sends contribute one row per message, broadcasts contribute one row per addressee and the UI collapses them by `message_id` into a stand-alone item. - `Store.ListSent` / `Service.ListSent` switched from `[]Message` to `[]InboxEntry`. SQL grows an INNER JOIN with `diplomail_recipients`. - Handler emits `userMailMessageDetailWire` items; the deprecated `userMailSentSummaryWire` is removed. - `openapi.yaml`: `UserMailSentList.items` now reference `UserMailMessageDetail`; the standalone `UserMailSentSummary` schema is dropped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4590 lines
139 KiB
YAML
4590 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.
|
|
UserMailInboxList:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/UserMailMessageDetail"
|
|
UserMailSentList:
|
|
description: |
|
|
Sender-side listing of personal messages authored by the
|
|
caller. Each item carries the same shape as inbox entries
|
|
(including the recipient snapshot); single sends contribute
|
|
one row per message, broadcasts contribute one row per
|
|
addressee so the in-game UI can collapse them by
|
|
`message_id` into a single stand-alone item.
|
|
type: object
|
|
additionalProperties: false
|
|
required: [items]
|
|
properties:
|
|
items:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/UserMailMessageDetail"
|
|
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
|