Files
website/server/swagger/swagger-output.json
wtclaude 10aed49bb6
All checks were successful
PR Checks / client-build (pull_request) Successful in 9m45s
PR Checks / server-tests (pull_request) Successful in 10m42s
PR Checks / bot-install (pull_request) Successful in 9m21s
feat(auth): self-service password reset (backend + web)
Add a full password-reset flow — the prerequisite for the Android app
(docs/android/PLAN.md §8.2), which hands off to the website for reset
rather than shipping a native screen.

Backend:
- password_resets table: stores only the sha256 hash of an opaque 32-byte
  token (mirrors user_invites / mobile_refresh_tokens), single-use, ~1h TTL.
- model/passwordResets + users.getActiveByEmail (email is non-unique, so a
  request can match several accounts, each emailed its own link).
- mailer.sendPasswordReset (fails soft when email is unconfigured).
- Endpoints: POST /auth/password/forgot (always a generic 200 — no account
  enumeration), GET|POST /auth/password/reset/:token. Confirming rotates the
  hash and revokes every session (web cutoff + mobile refresh tokens); it does
  not auto-login, so a 2FA account still passes TOTP next sign-in. Also serves
  SSO-only accounts (null hash) as their set-initial-password path.
- Dedicated request/confirm rate limiters. Swagger regenerated.

Web:
- ForgotPassword + ResetPassword pages, routes /account/forgot and
  /account/reset/:token, and a "Forgot your password?" link on the login page.

Tests: test/passwordResets.test.js (5). All server tests pass; client builds;
end-to-end smoketest against MariaDB passes (no-enumeration, single-use, hash
rotation, session revoke, login with the new password).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
2026-07-19 03:57:13 -05:00

13270 lines
345 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"openapi": "3.0.0",
"info": {
"title": "Runic Gateway API",
"version": "1.0.0",
"description": "REST API for the Runic Gateway website, wiki and admin panel — a private Ultima Online shard.\n\n### Authentication\n- **Web / admin panel** uses an httpOnly session cookie (`rg_token`) issued by `POST /api/v1/auth/login` (plus `/login/totp` when 2FA is enabled).\n- **Native / mobile clients** use bearer access tokens from `POST /api/v1/auth/mobile/login`, refreshed via `/auth/mobile/refresh`.\n\nEndpoints under `/api/v1/admin/**` require a valid session; some are further restricted to the `admin` role (editors are limited to content)."
},
"servers": [
{
"url": "/",
"description": "Same-origin (current host)"
},
{
"url": "http://localhost:3000",
"description": "Local development"
}
],
"tags": [
{
"name": "Health",
"description": "Liveness probe"
},
{
"name": "Auth",
"description": "Web session login/logout (cookie + TOTP)"
},
{
"name": "Auth · Mobile",
"description": "Native bearer-token login, refresh and logout"
},
{
"name": "Auth · SSO",
"description": "OAuth2 / OIDC provider discovery and redirect flow"
},
{
"name": "Public",
"description": "Unauthenticated site content (settings, posts, wiki, contact)"
},
{
"name": "Public · Shard",
"description": "Live shard data ingested from the uo-link sidecar (status, feed, economy, IDOC, characters)"
},
{
"name": "Admin · Account",
"description": "Self-service account security (2FA, linked identities)"
},
{
"name": "Player",
"description": "Self-service player accounts (register, credentials, 2FA, linked identities)"
},
{
"name": "Player · Shard",
"description": "Link an in-game account and read its roster / vendors (uo-link)"
},
{
"name": "Admin · Dashboard",
"description": "Dashboard summary and site mode"
},
{
"name": "Admin · Posts",
"description": "News / five-on-friday / newsletter / screenshots + uploads"
},
{
"name": "Admin · Wiki",
"description": "Wiki pages, categories, tags and revisions"
},
{
"name": "Admin · Settings",
"description": "Site settings (admin only)"
},
{
"name": "Admin · Activity",
"description": "Admin activity log"
},
{
"name": "Admin · Bot Activity",
"description": "Bot-scoring/ban state and emergency unban (admin only)"
},
{
"name": "Admin · Discord Bot",
"description": "Discord bot token/config and live status (admin only)"
},
{
"name": "Admin · Shard",
"description": "uo-link sidecar connection config, live status and town crier (admin only)"
},
{
"name": "Admin · Auth Providers",
"description": "SSO provider configuration (admin only)"
},
{
"name": "Admin · Users",
"description": "User management (admin only)"
}
],
"paths": {
"/api/health": {
"get": {
"tags": [
"Health"
],
"summary": "Liveness probe",
"description": "",
"responses": {
"200": {
"description": "Service is up",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "ok"
}
}
}
}
}
}
}
}
},
"/api/v1/auth/login": {
"post": {
"tags": [
"Auth"
],
"summary": "Log in with username and password",
"description": "On success sets the httpOnly session cookie. If the account has 2FA enabled, returns { totpRequired, challenge } instead and no cookie is set — complete login at POST /login/totp. Rate limited and behind bot/backoff guards.",
"responses": {
"200": {
"description": "Session issued, or TOTP challenge required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Incorrect username or password",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Forbidden"
},
"429": {
"description": "Too many attempts (rate limited / backoff)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginRequest"
}
}
}
}
}
},
"/api/v1/auth/register": {
"post": {
"tags": [
"Auth"
],
"summary": "Register a player account",
"description": "Creates a self-service player account and logs it in (sets the session cookie). Available only when an admin has enabled password registration (player_registration = password|both); otherwise returns 403. Rate limited and behind bot/backoff guards; a hidden honeypot field must stay empty.",
"responses": {
"200": {
"description": "Account created and session issued",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
},
"400": {
"description": "Validation error or unavailable username",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"403": {
"description": "Registration is not open",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Username already taken",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many attempts (rate limited / backoff)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RegisterRequest"
}
}
}
}
}
},
"/api/v1/auth/login/totp": {
"post": {
"tags": [
"Auth"
],
"summary": "Complete login with a TOTP code",
"description": "Second step for 2FA accounts. Exchange the challenge from /login plus the current authenticator code for a session cookie.",
"responses": {
"200": {
"description": "Session issued",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Invalid code or expired challenge",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many attempts (rate limited / backoff)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpLoginRequest"
}
}
}
}
}
},
"/api/v1/auth/invite/{token}": {
"get": {
"tags": [
"Auth"
],
"summary": "Look up an email invite by token",
"description": "Returns the pre-assigned email + role for a valid, pending, unexpired invite so the accept form can render. 404 for anything not currently acceptable.",
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Invite details",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"role": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Invalid or expired invite",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/auth/invite/{token}/accept": {
"post": {
"tags": [
"Auth"
],
"summary": "Accept an email invite (creates the account at the invited role)",
"description": "Creates the website user at the invites pre-assigned role and logs them in (sets the session cookie). Bypasses the player_registration gate — the invite is its own authority. Rate limited + honeypot-guarded like registration.",
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Account created and session issued",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LoginResponse"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"404": {
"description": "Invalid or expired invite",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Username taken or invite already used",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {}
}
},
"/api/v1/auth/password/forgot": {
"post": {
"tags": [
"Auth"
],
"summary": "Request a password-reset link by email",
"description": "Emails a single-use, ~1h reset link to every active account on the address. Always returns the same generic 200 whether or not the email matches (no account enumeration). Email is non-unique, so multiple accounts may each receive a link naming their username. Rate limited per IP.",
"responses": {
"200": {
"description": "Generic acknowledgement (sent if the account exists)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"429": {
"description": "Too many requests",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"email"
],
"properties": {
"email": {
"type": "string",
"format": "email"
}
}
}
}
}
}
}
},
"/api/v1/auth/password/reset/{token}": {
"get": {
"tags": [
"Auth"
],
"summary": "Validate a password-reset link",
"description": "Returns the target username for a valid, pending, unexpired reset link so the reset form can render. 404 for anything not currently usable (never distinguishes expired from used from never-existed).",
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Reset link is valid",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"username": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Invalid or expired reset link",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
},
"post": {
"tags": [
"Auth"
],
"summary": "Set a new password from a reset link",
"description": "Consumes the single-use link and sets the new password. Rotates the hash and revokes every existing session (web + mobile). Does NOT sign the user in — they log in fresh afterwards (so a 2FA account still passes TOTP). Rate limited per IP.",
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Password changed",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"404": {
"description": "Invalid, expired, or already-used reset link",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many attempts",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"password"
],
"properties": {
"password": {
"type": "string",
"minLength": 8,
"maxLength": 64
}
}
}
}
}
}
}
},
"/api/v1/auth/logout": {
"post": {
"tags": [
"Auth"
],
"summary": "Log out (clear the cookie and revoke this session)",
"description": "",
"responses": {
"200": {
"description": "Logged out",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
}
}
}
},
"/api/v1/auth/me": {
"get": {
"tags": [
"Auth"
],
"summary": "Current authenticated user",
"description": "",
"responses": {
"200": {
"description": "The signed-in user",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/auth/mobile/login": {
"post": {
"tags": [
"Auth · Mobile"
],
"summary": "Native login → access + refresh tokens",
"description": "Bearer-token login for native clients. Single-request 2FA: if the account has TOTP on and no/invalid code is supplied, returns 401 { totpRequired: true } and the client retries with a code.",
"responses": {
"200": {
"description": "Access + refresh tokens",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MobileTokenResponse"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Invalid credentials, or a TOTP code is required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many attempts (rate limited / backoff)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MobileLoginRequest"
}
}
}
}
}
},
"/api/v1/auth/mobile/refresh": {
"post": {
"tags": [
"Auth · Mobile"
],
"summary": "Rotate a refresh token for a fresh token pair",
"description": "Refresh tokens are single-use: the presented token is revoked and a new access + refresh pair is issued. Reusing a rotated token fails with 401.",
"responses": {
"200": {
"description": "New access + refresh tokens",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MobileTokenResponse"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Invalid or expired session",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many refresh attempts",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MobileRefreshRequest"
}
}
}
}
}
},
"/api/v1/auth/mobile/logout": {
"post": {
"tags": [
"Auth · Mobile"
],
"summary": "Revoke the current (or all) refresh tokens",
"description": "Requires a valid bearer access token. Revokes the given refresh token, or every session for the user when { all: true }. Idempotent.",
"responses": {
"200": {
"description": "Logged out",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Missing or invalid bearer token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MobileLogoutRequest"
}
}
}
}
}
},
"/api/v1/auth/providers": {
"get": {
"tags": [
"Auth · SSO"
],
"summary": "List enabled SSO providers",
"description": "Public discovery used by the login page to render provider buttons. Never exposes secrets.",
"responses": {
"200": {
"description": "Enabled, valid providers",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Provider"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/auth/sso/{provider}/start": {
"get": {
"tags": [
"Auth · SSO"
],
"summary": "Begin SSO login (redirect to the IdP)",
"description": "Sets a short-lived signed transaction cookie and 302-redirects to the provider authorize URL. On error redirects back to the login page with an sso_error query param.",
"parameters": [
{
"name": "provider",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id (e.g. google, discord)."
},
{
"name": "returnTo",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"description": "Internal /admin path to return to after login."
}
],
"responses": {
"302": {
"description": "Redirect to the identity provider (or back to the login page on error)"
}
}
}
},
"/api/v1/auth/sso/{provider}/link": {
"get": {
"tags": [
"Auth · SSO"
],
"summary": "Begin linking an SSO identity to the current account",
"description": "Requires an authenticated session; the signed transaction captures the acting user so the callback can attach the external identity.",
"parameters": [
{
"name": "provider",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id (e.g. google, discord)."
}
],
"responses": {
"302": {
"description": "Redirect to the identity provider (or back to the account page on error)"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/auth/sso/{provider}/callback": {
"get": {
"tags": [
"Auth · SSO"
],
"summary": "OAuth redirect target — completes login or linking",
"description": "The provider redirects here with code + state. On success sets the session cookie (login) or links the identity (link), then 302-redirects into /admin. Login is link-only: unknown identities are refused (sso_error=not_linked).",
"parameters": [
{
"name": "provider",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id (e.g. google, discord)."
},
{
"name": "error",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "code",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"description": "OAuth authorization code."
},
{
"name": "state",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"description": "OAuth state (matched against the tx cookie)."
}
],
"responses": {
"302": {
"description": "Redirect into /admin on success, or back to login/account with an error code"
}
}
}
},
"/api/v1/auth/sso/totp": {
"post": {
"tags": [
"Auth · SSO"
],
"summary": "Complete an SSO login with a TOTP code",
"description": "Second step when a linked account has 2FA enabled. Reads the staged pending-TOTP cookie set by the callback plus the current authenticator code, and on success sets the session cookie. Rate limited and behind bot/backoff guards.",
"responses": {
"200": {
"description": "Session issued",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/SafeUser"
},
"returnTo": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Invalid code or expired challenge",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Forbidden"
},
"429": {
"description": "Too many attempts (rate limited / backoff)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"code"
],
"properties": {
"code": {
"type": "string"
}
}
}
}
}
}
}
},
"/api/v1/public/settings": {
"get": {
"tags": [
"Public"
],
"summary": "Public site settings",
"description": "Whitelisted, non-sensitive settings the client needs to render the site.",
"responses": {
"200": {
"description": "Key/value settings",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/status": {
"get": {
"tags": [
"Public"
],
"summary": "Site mode / status",
"description": "Current site mode (live or maintenance) so the client can show the maintenance page.",
"responses": {
"200": {
"description": "Site status",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublicStatus"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/contact": {
"post": {
"tags": [
"Public"
],
"summary": "Send a contact message",
"description": "Emails the site owner (or falls back to a mailto). Rate limited.",
"responses": {
"200": {
"description": "Message sent",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"429": {
"description": "Too many messages (rate limited)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"502": {
"description": "Mail delivery failed",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactRequest"
}
}
}
}
}
},
"/api/v1/public/posts/{category}": {
"get": {
"tags": [
"Public"
],
"summary": "List published posts in a category",
"description": "Gated by site mode: during maintenance only admins with a valid session see content.",
"parameters": [
{
"name": "category",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "news | five-on-friday | newsletter | screenshots"
}
],
"responses": {
"200": {
"description": "Published posts",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
}
},
"404": {
"description": "Unknown category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/posts/{category}/{idOrSlug}": {
"get": {
"tags": [
"Public"
],
"summary": "Get a single published post",
"description": "",
"parameters": [
{
"name": "category",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Post category."
},
{
"name": "idOrSlug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Numeric id or slug."
}
],
"responses": {
"200": {
"description": "The post",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"404": {
"description": "Unknown category or post not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/wiki": {
"get": {
"tags": [
"Public"
],
"summary": "List published wiki pages",
"description": "",
"parameters": [
{
"name": "q",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "category",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "tag",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Published wiki pages",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/wiki/categories": {
"get": {
"tags": [
"Public"
],
"summary": "List wiki categories",
"description": "",
"responses": {
"200": {
"description": "Wiki categories",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/WikiCategory"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/wiki/tags": {
"get": {
"tags": [
"Public"
],
"summary": "List wiki tags",
"description": "",
"responses": {
"200": {
"description": "Wiki tags",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/wiki/{slug}": {
"get": {
"tags": [
"Public"
],
"summary": "Get a single published wiki page",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
}
],
"responses": {
"200": {
"description": "The wiki page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/pages/{id}/preview/{token}": {
"get": {
"tags": [
"Public"
],
"summary": "Render a page from a draft-preview token",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Page id."
},
{
"name": "token",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Preview token from POST /admin/pages/:id/preview."
}
],
"responses": {
"200": {
"description": "The page (any status)",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"404": {
"description": "Token invalid/expired or page missing",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/pages/{slug}": {
"get": {
"tags": [
"Public"
],
"summary": "Get a published CMS page by slug",
"description": "Drafts 404 for the public; staff sessions see drafts. Gated by site mode.",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Page slug."
}
],
"responses": {
"200": {
"description": "The page",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Service Unavailable"
}
}
}
},
"/api/v1/public/shard/status": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Shard connection state, online count and latest economy",
"description": "",
"responses": {
"200": {
"description": "Shard status",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardStatus"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/feed": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Recent notable shard events (from the ingested log)",
"description": "",
"parameters": [
{
"name": "kind",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"description": "Filter to a single event kind, e.g. vendor.sale."
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer"
},
"description": "Max rows (default 100, max 1000)."
}
],
"responses": {
"200": {
"description": "Events, newest first",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardEvent"
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/economy": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Gold-supply time series (oldest → newest)",
"description": "",
"parameters": [
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer"
},
"description": "Max samples (default 100, max 1000)."
}
],
"responses": {
"200": {
"description": "Economy samples",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardEconomyPoint"
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/online": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Staff online now (linked staff accounts; location is admin/moderator-only)",
"description": "",
"responses": {
"200": {
"description": "Online players",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardOnlinePlayer"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/idoc": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Houses currently in danger (IDOC)",
"description": "",
"responses": {
"200": {
"description": "IDOC houses",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardHouse"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/champs": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Current champion-spawn board (all categories)",
"description": "The live board of every champion / mini-champ / sea-boss spawn. Update in place via the champ.update / champ.remove frames on /shard/stream.",
"responses": {
"200": {
"description": "Champion spawns, ordered by name",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/guilds": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Current guild board (rosters, alliances, leaders)",
"description": "The live board of every guild. Update in place via the guild.update / guild.remove / guild.join frames on /shard/stream.",
"responses": {
"200": {
"description": "Guilds, ordered by name",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/governors": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Current town-governor board (City Loyalty)",
"description": "One entry per city with its governor and election phase. Empty if the shard does not run the City Loyalty system. Live via city.update on /shard/stream.",
"responses": {
"200": {
"description": "Cities, ordered by name",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/governors/{city}/history": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Governor term history for a city",
"description": "",
"parameters": [
{
"name": "city",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "City name, e.g. Britain."
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer"
},
"description": "Max terms (default 100, max 500)."
}
],
"responses": {
"200": {
"description": "Terms, newest first",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/presence": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Online population aggregate (count + per-facet + per-region)",
"description": "The latest presence.online snapshot powering the \"Players Online\" widget. Live via presence.online on /shard/stream.",
"responses": {
"200": {
"description": "Population snapshot",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/houses": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "House registry (owner, co-owners, price, decay)",
"description": "Every house seen via the house.update registry feed. `price` is the placement value, not a for-sale flag. Live via house.update / house.remove on /shard/stream.",
"responses": {
"200": {
"description": "Houses, ordered by name",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardHouse"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
}
}
},
"/api/v1/public/shard/stream": {
"get": {
"tags": [
"Public · Shard"
],
"summary": "Live shard event stream (Server-Sent Events, public/safe kinds)",
"description": "text/event-stream of curated live events. Sensitive kinds (staff audit, cheat detection, login attempts, IPs) are NOT sent on this channel.",
"responses": {
"200": {
"description": "An SSE stream (Content-Type: text/event-stream)."
}
}
}
},
"/api/v1/admin/account": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Get the current account (self)",
"description": "",
"responses": {
"200": {
"description": "The account",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccountStatus"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/account/totp/setup": {
"post": {
"tags": [
"Admin · Account"
],
"summary": "Begin 2FA enrollment (returns secret + QR)",
"description": "",
"responses": {
"200": {
"description": "otpauth URL and QR data to scan",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpSetup"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Two-factor already enabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/account/totp/enable": {
"post": {
"tags": [
"Admin · Account"
],
"summary": "Enable 2FA by confirming a code",
"description": "",
"responses": {
"200": {
"description": "2FA enabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpState"
}
}
}
},
"400": {
"description": "Setup not started, or invalid code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Two-factor already enabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpCodeRequest"
}
}
}
}
}
},
"/api/v1/admin/account/totp/disable": {
"post": {
"tags": [
"Admin · Account"
],
"summary": "Disable 2FA by confirming a code",
"description": "",
"responses": {
"200": {
"description": "2FA disabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpState"
}
}
}
},
"400": {
"description": "Not enabled, or invalid code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpCodeRequest"
}
}
}
}
}
},
"/api/v1/admin/account/identities": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "List linked SSO identities (self)",
"description": "",
"responses": {
"200": {
"description": "Linked identities",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedIdentity"
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/account/identities/{provider}": {
"delete": {
"tags": [
"Admin · Account"
],
"summary": "Unlink an SSO identity (self)",
"description": "",
"parameters": [
{
"name": "provider",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id."
}
],
"responses": {
"200": {
"description": "Unlinked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnlinkedFlag"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No linked account for that provider",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/link": {
"post": {
"tags": [
"Admin · Account"
],
"summary": "Link an in-game account with a one-time code (self)",
"description": "",
"responses": {
"200": {
"description": "Linked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardLinkResult"
}
}
}
},
"400": {
"description": "Unknown or expired code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Service Unavailable"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardLinkRequest"
}
}
}
}
}
},
"/api/v1/admin/shard/accounts": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "List the callers linked game accounts (self)",
"description": "",
"responses": {
"200": {
"description": "Linked accounts",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardLink"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/roster/{account}": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Character roster for an account (self; admins: any account)",
"description": "",
"parameters": [
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "A game account linked to the caller."
}
],
"responses": {
"200": {
"description": "Account roster",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Account not linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/vendors/{account}": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Player vendors for an account (self; admins: any account)",
"description": "",
"parameters": [
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "A game account linked to the caller."
}
],
"responses": {
"200": {
"description": "Vendor snapshot",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Account not linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/char/{serial}": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Character sheet (self-linked characters; admins: any character)",
"description": "",
"parameters": [
{
"name": "serial",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Mobile serial, e.g. 0x24C."
}
],
"responses": {
"200": {
"description": "Character profile",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Character not on an account linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not Found"
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Service Unavailable"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/sales": {
"get": {
"tags": [
"Admin · Account"
],
"summary": "Recent player-vendor sales for the callers linked accounts (self)",
"description": "",
"responses": {
"200": {
"description": "Vendor sales",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardVendorSale"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/account": {
"post": {
"tags": [
"Admin · Account"
],
"summary": "Create a game account and link it to the caller (staff self-service)",
"description": "Same as POST /player/shard/account but for a signed-in staff user — provisions a game account (own username + password) and links it. Gated by game_account_signup + the shards mode; the password is never stored or logged.",
"responses": {
"201": {
"description": "Account created and linked",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Game-account signup unavailable (site or shard)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Account name already taken",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {}
}
},
"/api/v1/admin/shard/kick": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Kick every live session of an account (admin/moderator)",
"description": "",
"responses": {
"200": {
"description": "Kicked",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Protected target or write plane disabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"account": {
"type": "string"
},
"serial": {
"type": "string"
}
}
}
}
}
}
}
},
"/api/v1/admin/shard/ban": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Ban an account, timed or indefinite (admin/moderator)",
"description": "",
"responses": {
"200": {
"description": "Banned",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Protected target or write plane disabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"account": {
"type": "string"
},
"serial": {
"type": "string"
},
"durationSec": {
"type": "integer"
},
"reason": {
"type": "string"
}
}
}
}
}
}
}
},
"/api/v1/admin/shard/unban": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Clear an account ban (admin/moderator)",
"description": "",
"responses": {
"200": {
"description": "Unbanned",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"account": {
"type": "string"
}
},
"required": [
"account"
]
}
}
}
}
}
},
"/api/v1/admin/shard/broadcast": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Broadcast a system message to everyone online (admin/moderator)",
"description": "",
"responses": {
"200": {
"description": "Broadcast",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"text": {
"type": "string"
},
"hue": {
"type": "integer"
}
},
"required": [
"text"
]
}
}
}
}
}
},
"/api/v1/admin/shard/pages": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Open help-page (support) queue (admin/moderator)",
"description": "",
"responses": {
"200": {
"description": "Open pages",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/pages/{id}/respond": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Reply to a help page, optionally closing it (admin/moderator)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Page id (sender serial)."
}
],
"responses": {
"200": {
"description": "Responded",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Unknown page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string"
},
"close": {
"type": "boolean"
}
},
"required": [
"message"
]
}
}
}
}
}
},
"/api/v1/admin/shard/pages/{id}/close": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Resolve a help page without a reply (admin/moderator)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Page id (sender serial)."
}
],
"responses": {
"200": {
"description": "Closed",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/audit": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Recent in-game moderation audit events (admin/moderator)",
"description": "",
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "admin.audit events, newest first",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardEvent"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/shard/houses": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Full house registry — owner, price, decay (admin/moderator)",
"description": "The complete house registry. The public endpoint shows only IDOC houses with location; this staff view carries owner/price/co-owner/decay detail.",
"responses": {
"200": {
"description": "Houses, ordered by name",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardHouse"
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/dashboard": {
"get": {
"tags": [
"Admin · Dashboard"
],
"summary": "Dashboard summary counts",
"description": "",
"responses": {
"200": {
"description": "Summary: site mode, last change, post/user counts and recent activity",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"site_mode": {
"type": "string",
"example": "live"
},
"last_change": {
"type": "object",
"properties": {
"at": {
"type": "string",
"nullable": true
},
"by": {
"type": "string",
"nullable": true
}
}
},
"counts": {
"type": "object",
"properties": {
"posts": {
"type": "object",
"additionalProperties": true
},
"users": {
"type": "integer"
}
}
},
"recent_activity": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/site-mode": {
"put": {
"tags": [
"Admin · Dashboard"
],
"summary": "Set site mode (admin only)",
"description": "Switch the site between live and maintenance.",
"responses": {
"200": {
"description": "Updated site mode",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SiteModeState"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SiteModeRequest"
}
}
}
}
}
},
"/api/v1/admin/posts": {
"get": {
"tags": [
"Admin · Posts"
],
"summary": "List all posts (including unpublished)",
"description": "",
"parameters": [
{
"name": "category",
"in": "query",
"required": false,
"schema": {
"type": "string"
},
"description": "Optional category filter."
}
],
"responses": {
"200": {
"description": "Posts",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Post"
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Posts"
],
"summary": "Create a post",
"description": "",
"responses": {
"201": {
"description": "Created post",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"400": {
"description": "Validation error or unknown category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PostCreateRequest"
}
}
}
}
}
},
"/api/v1/admin/posts/upload": {
"post": {
"tags": [
"Admin · Posts"
],
"summary": "Upload a post image (multipart)",
"description": "",
"responses": {
"201": {
"description": "Stored image URL",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"image_url": {
"type": "string",
"example": "/uploads/1700000000-abcd.png"
}
}
}
}
}
},
"400": {
"description": "No image / disallowed type",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"image": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
}
},
"/api/v1/admin/uploads": {
"post": {
"tags": [
"Admin · Posts"
],
"summary": "Upload an image for rich-text editors (multipart)",
"description": "",
"responses": {
"201": {
"description": "Stored file URL",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UploadResponse"
}
}
}
},
"400": {
"description": "No file / disallowed type",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"properties": {
"image": {
"type": "string",
"format": "binary"
}
}
}
}
}
}
}
},
"/api/v1/admin/posts/{id}": {
"get": {
"tags": [
"Admin · Posts"
],
"summary": "Get a post by id",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Post id."
}
],
"responses": {
"200": {
"description": "The post",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Posts"
],
"summary": "Update a post",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Post id."
}
],
"responses": {
"200": {
"description": "Updated post",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"400": {
"description": "Validation error or unknown category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PostCreateRequest"
}
}
}
}
},
"delete": {
"tags": [
"Admin · Posts"
],
"summary": "Delete a post",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Post id."
}
],
"responses": {
"200": {
"description": "Deleted (echoes the id)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedId"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/posts/{id}/publish": {
"patch": {
"tags": [
"Admin · Posts"
],
"summary": "Publish / unpublish a post",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Post id."
}
],
"responses": {
"200": {
"description": "Updated post",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Post"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublishRequest"
}
}
}
}
}
},
"/api/v1/admin/posts/{id}/announce": {
"get": {
"tags": [
"Admin · Posts"
],
"summary": "Get the announcement pipeline status for a post",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Post id."
}
],
"responses": {
"200": {
"description": "The announce job for the post, or null if never announced",
"content": {
"application/json": {
"schema": {
"type": "object",
"nullable": true,
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/posts/{id}/announce/retry": {
"post": {
"tags": [
"Admin · Posts"
],
"summary": "Retry one announcement delivery leg (town crier or Discord)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Post id."
}
],
"responses": {
"200": {
"description": "Updated announce job",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "No announcement job for this post",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"leg": {
"type": "string",
"enum": [
"towncrier",
"discord"
]
}
},
"required": [
"leg"
]
}
}
}
}
}
},
"/api/v1/admin/wiki/categories": {
"get": {
"tags": [
"Admin · Wiki"
],
"summary": "List wiki categories",
"description": "",
"responses": {
"200": {
"description": "Wiki categories",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/WikiCategory"
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Wiki"
],
"summary": "Create a wiki category",
"description": "",
"responses": {
"201": {
"description": "Created category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiCategory"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Slug already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiCategoryCreateRequest"
}
}
}
}
}
},
"/api/v1/admin/wiki/categories/{id}": {
"put": {
"tags": [
"Admin · Wiki"
],
"summary": "Update a wiki category",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id."
}
],
"responses": {
"200": {
"description": "Updated category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiCategory"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Slug already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiCategoryCreateRequest"
}
}
}
}
},
"delete": {
"tags": [
"Admin · Wiki"
],
"summary": "Delete a wiki category",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Category id."
}
],
"responses": {
"200": {
"description": "Deleted (echoes the id)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedId"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/wiki/tags": {
"get": {
"tags": [
"Admin · Wiki"
],
"summary": "List wiki tags",
"description": "",
"responses": {
"200": {
"description": "Wiki tags",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/wiki": {
"get": {
"tags": [
"Admin · Wiki"
],
"summary": "List all wiki pages (including unpublished)",
"description": "",
"parameters": [
{
"name": "q",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "category",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "tag",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "status",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Wiki pages",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Wiki"
],
"summary": "Create a wiki page",
"description": "",
"responses": {
"201": {
"description": "Created wiki page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
},
"400": {
"description": "Validation error or unknown category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Slug already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPageCreateRequest"
}
}
}
}
}
},
"/api/v1/admin/wiki/{slug}": {
"get": {
"tags": [
"Admin · Wiki"
],
"summary": "Get a wiki page by slug",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
}
],
"responses": {
"200": {
"description": "The wiki page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Wiki"
],
"summary": "Update a wiki page (creates a revision)",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
}
],
"responses": {
"200": {
"description": "Updated wiki page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
},
"400": {
"description": "Validation error or unknown category",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/WikiPageCreateRequest"
},
{
"type": "object",
"properties": {
"change_note": {
"type": "string",
"maxLength": 280
}
}
}
]
}
}
}
}
},
"delete": {
"tags": [
"Admin · Wiki"
],
"summary": "Delete a wiki page",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
}
],
"responses": {
"200": {
"description": "Deleted (echoes the slug)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedSlug"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/wiki/{slug}/publish": {
"patch": {
"tags": [
"Admin · Wiki"
],
"summary": "Publish / unpublish a wiki page",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
}
],
"responses": {
"200": {
"description": "Updated wiki page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublishRequest"
}
}
}
}
}
},
"/api/v1/admin/wiki/{slug}/revisions": {
"get": {
"tags": [
"Admin · Wiki"
],
"summary": "List revisions of a wiki page",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
}
],
"responses": {
"200": {
"description": "Revisions",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/wiki/{slug}/revisions/{id}": {
"get": {
"tags": [
"Admin · Wiki"
],
"summary": "Get a single wiki revision",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
},
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Revision id."
}
],
"responses": {
"200": {
"description": "The revision",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/wiki/{slug}/revisions/{id}/restore": {
"post": {
"tags": [
"Admin · Wiki"
],
"summary": "Restore a wiki page to a revision",
"description": "",
"parameters": [
{
"name": "slug",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Wiki page slug."
},
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Revision id to restore."
}
],
"responses": {
"200": {
"description": "Restored wiki page",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WikiPage"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/pages": {
"get": {
"tags": [
"Admin · Pages"
],
"summary": "List all CMS pages (summaries)",
"description": "",
"responses": {
"200": {
"description": "Page summaries",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Pages"
],
"summary": "Create a CMS page",
"description": "",
"responses": {
"201": {
"description": "Created page",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Invalid slug / title / blocks / metadata / settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Slug already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"slug": {
"type": "string"
},
"title": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"draft",
"published"
]
},
"blocks": {
"type": "array",
"items": {
"type": "object"
}
},
"metadata": {
"type": "object"
},
"settings": {
"type": "object"
}
}
}
}
}
}
}
},
"/api/v1/admin/pages/{id}": {
"get": {
"tags": [
"Admin · Pages"
],
"summary": "Get a CMS page by id (full, incl. blocks)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Page id."
}
],
"responses": {
"200": {
"description": "The page",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"patch": {
"tags": [
"Admin · Pages"
],
"summary": "Update a CMS page (title, status, blocks, metadata, settings)",
"description": "slug is immutable; disabling protection is rejected here (use /unprotect).",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Page id."
}
],
"responses": {
"200": {
"description": "Updated page",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Validation error (slug immutable, invalid blocks, etc.)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Disabling protection requires /unprotect",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"delete": {
"tags": [
"Admin · Pages"
],
"summary": "Delete a CMS page (blocked if protected)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Page id."
}
],
"responses": {
"200": {
"description": "Deleted (echoes the id)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedId"
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Page is protected",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/pages/{id}/unprotect": {
"post": {
"tags": [
"Admin · Pages"
],
"summary": "Disable page protection (password step-up re-auth)",
"description": "Verifies the current admin password server-side, then flips protected → false.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Page id."
}
],
"responses": {
"200": {
"description": "Updated page (protected=false)",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Password incorrect",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": [
"password"
]
}
}
}
}
}
},
"/api/v1/admin/pages/{id}/preview": {
"post": {
"tags": [
"Admin · Pages"
],
"summary": "Mint a 1h draft-preview link for a page",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Page id."
}
],
"responses": {
"200": {
"description": "Preview token + path",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"token": {
"type": "string"
},
"expiresInSeconds": {
"type": "integer"
},
"path": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/settings": {
"get": {
"tags": [
"Admin · Settings"
],
"summary": "Get all site settings (admin only)",
"description": "",
"responses": {
"200": {
"description": "All settings",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Settings"
],
"summary": "Update site settings (admin only)",
"description": "",
"responses": {
"200": {
"description": "Updated settings",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Body must be an object of key/value settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true,
"description": "An object of key/value settings."
}
}
}
}
}
},
"/api/v1/admin/activity": {
"get": {
"tags": [
"Admin · Activity"
],
"summary": "List recent admin activity",
"description": "",
"parameters": [
{
"name": "offset",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"required": false,
"schema": {
"type": "integer"
},
"description": "Max rows to return."
}
],
"responses": {
"200": {
"description": "Activity entries",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/bot-activity": {
"get": {
"tags": [
"Admin · Bot Activity"
],
"summary": "Bot-scoring / ban state and recent events (admin only)",
"description": "",
"responses": {
"200": {
"description": "Banned IPs, scores and recent events",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/bot-activity/unban": {
"post": {
"tags": [
"Admin · Bot Activity"
],
"summary": "Emergency unban an IP (admin only)",
"description": "",
"responses": {
"200": {
"description": "Unbanned (echoes the ip and whether an entry was cleared)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnbanResult"
}
}
}
},
"400": {
"description": "Invalid IP",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnbanRequest"
}
}
}
}
}
},
"/api/v1/admin/discord-bot/config": {
"get": {
"tags": [
"Admin · Discord Bot"
],
"summary": "Get Discord bot config + live status (admin only)",
"description": "",
"responses": {
"200": {
"description": "Masked config + live status",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Discord Bot"
],
"summary": "Save Discord bot config (admin only)",
"description": "token is write-only — omit/blank it to keep the existing one unchanged.",
"responses": {
"200": {
"description": "Updated config + live status",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Validation error, invalid token, or missing token while enabling",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"guildId": {
"type": "string"
},
"token": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
}
}
}
}
}
}
},
"/api/v1/admin/email/config": {
"get": {
"tags": [
"Admin · Email"
],
"summary": "Get email delivery config + status (admin only)",
"description": "",
"responses": {
"200": {
"description": "Config (refresh token stripped) + status",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Email"
],
"summary": "Update email delivery config (admin only)",
"description": "Set the From display name and enabled toggle. Enabling requires a connected Gmail account.",
"responses": {
"200": {
"description": "Updated config",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Cannot enable before connecting a mailbox",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"senderName": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
}
}
}
}
}
}
},
"/api/v1/admin/email/connect/start": {
"get": {
"tags": [
"Admin · Email"
],
"summary": "Begin the Gmail OAuth2 connect flow (admin only)",
"description": "Returns { url } to redirect the browser to Google. Reuses the google SSO OAuth client.",
"responses": {
"200": {
"description": "Authorization URL",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Google OAuth client not configured",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/email/connect/callback": {
"get": {
"tags": [
"Admin · Email"
],
"summary": "OAuth2 callback — stores the refresh token, redirects to Settings",
"description": "",
"parameters": [
{
"name": "code",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "state",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "error",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"302": {
"description": "Redirect back to /admin/settings"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/email/test": {
"post": {
"tags": [
"Admin · Email"
],
"summary": "Send a test email (admin only)",
"description": "",
"responses": {
"200": {
"description": "Sent",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sent": {
"type": "boolean"
},
"to": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"502": {
"description": "Send failed / not configured",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"format": "email"
}
}
}
}
}
}
}
},
"/api/v1/admin/email/disconnect": {
"post": {
"tags": [
"Admin · Email"
],
"summary": "Disconnect Gmail and disable email (admin only)",
"description": "",
"responses": {
"200": {
"description": "Disconnected config",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/auth/providers": {
"get": {
"tags": [
"Admin · Auth Providers"
],
"summary": "List configured SSO providers (admin only)",
"description": "",
"responses": {
"200": {
"description": "Providers (secrets stripped)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ProviderConfig"
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Auth Providers"
],
"summary": "Create a custom SSO provider (admin only)",
"description": "Built-in providers (google, discord) are configured via PUT, not created here.",
"responses": {
"201": {
"description": "Created provider",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderConfig"
}
}
}
},
"400": {
"description": "Validation error, or a built-in/invalid kind",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Provider id already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderCreateRequest"
}
}
}
}
}
},
"/api/v1/admin/auth/providers/{id}": {
"put": {
"tags": [
"Admin · Auth Providers"
],
"summary": "Update an SSO provider (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id."
}
],
"responses": {
"200": {
"description": "Updated provider",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderConfig"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Provider not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderCreateRequest"
}
}
}
}
},
"delete": {
"tags": [
"Admin · Auth Providers"
],
"summary": "Delete a custom SSO provider (admin only)",
"description": "Built-in providers cannot be deleted — disable them instead.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id."
}
],
"responses": {
"200": {
"description": "Deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedFlag"
}
}
}
},
"400": {
"description": "Built-in provider cannot be deleted",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Provider not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/stats/summary": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Moderation action counts for 24h/7d/30d (admin or moderator)",
"description": "",
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/recent": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Recent moderation actions, optionally filtered by type",
"description": "",
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/search": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Look up moderated users by Discord id or username snapshot",
"description": "",
"parameters": [
{
"name": "q",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/members": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Recent member join/leave events (optionally filtered by type)",
"description": "",
"parameters": [
{
"name": "type",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/filter-hits": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Recent automated content-filter hits",
"description": "",
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/spam-hits": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Recent automated spam-detection hits",
"description": "",
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/user/{discordId}": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Per-user moderation summary (counts, latest tag, linked account)",
"description": "",
"parameters": [
{
"name": "discordId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/user/{discordId}/actions": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Full moderation action history for a user",
"description": "",
"parameters": [
{
"name": "discordId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/moderation/user/{discordId}/notes": {
"get": {
"tags": [
"Admin · Moderation"
],
"summary": "Staff notes for a user (admin_only notes hidden from moderators)",
"description": "",
"parameters": [
{
"name": "discordId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Moderation"
],
"summary": "Add a staff note (admin_only visibility requires the admin role)",
"description": "",
"parameters": [
{
"name": "discordId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"201": {
"description": "Created"
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"visibility": {
"example": "any"
},
"body": {
"example": "any"
}
}
}
}
}
}
}
},
"/api/v1/admin/users": {
"get": {
"tags": [
"Admin · Users"
],
"summary": "List users (admin only)",
"description": "",
"responses": {
"200": {
"description": "Users",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"post": {
"tags": [
"Admin · Users"
],
"summary": "Create a user (admin only)",
"description": "",
"responses": {
"201": {
"description": "Created user",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Username already taken",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreateRequest"
}
}
}
}
}
},
"/api/v1/admin/users/{id}": {
"put": {
"tags": [
"Admin · Users"
],
"summary": "Update a user (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Updated user",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Validation error, or cannot demote the last admin",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Username already taken",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreateRequest"
}
}
}
}
},
"delete": {
"tags": [
"Admin · Users"
],
"summary": "Delete a user (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Deleted (echoes the id)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedId"
}
}
}
},
"400": {
"description": "Cannot delete your own account or the last admin",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"get": {
"tags": [
"Admin · Users"
],
"summary": "Get a single user (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "The user",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/users/{id}/shard/accounts": {
"get": {
"tags": [
"Admin · Users"
],
"summary": "A users linked game accounts (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Linked accounts",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardLink"
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/users/{id}/shard/sales": {
"get": {
"tags": [
"Admin · Users"
],
"summary": "Recent vendor sales on a users accounts (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Vendor sales",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardVendorSale"
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/users/{id}/shard/houses": {
"get": {
"tags": [
"Admin · Users"
],
"summary": "Houses owned by a users accounts (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Houses (IDOC first)",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/users/{id}/shard/online": {
"get": {
"tags": [
"Admin · Users"
],
"summary": "A users characters currently online (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Online characters",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/users/{id}/shard/standing": {
"get": {
"tags": [
"Admin · Users"
],
"summary": "A users shard standing — governorships held and guilds led (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
}
],
"responses": {
"200": {
"description": "Standing { governorOf, guildsLed }",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/users/{id}/shard/link/{account}": {
"delete": {
"tags": [
"Admin · Users"
],
"summary": "Unlink a game account from this user (admin only)",
"description": "Severs a game accounts tie to the website user from the site side (sidecar DELETE /link/{account}) and drops the local mirror. actor is stamped from the session.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "User id."
},
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Game account to unlink."
}
],
"responses": {
"200": {
"description": "Unlinked",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"account": {
"type": "string"
},
"unlinked": {
"type": "boolean"
}
}
}
}
}
},
"400": {
"description": "Bad Request"
},
"403": {
"description": "Protected staff account (refused by shard)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not linked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Service Unavailable"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/invites": {
"post": {
"tags": [
"Admin · Invites"
],
"summary": "Create and email an account invite at a chosen access level",
"description": "",
"responses": {
"201": {
"description": "Invite created",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Validation error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {}
},
"get": {
"tags": [
"Admin · Invites"
],
"summary": "List recent invites (no tokens)",
"description": "",
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Invites, newest first",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/invites/{id}": {
"delete": {
"tags": [
"Admin · Invites"
],
"summary": "Revoke a pending invite",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "integer"
},
"description": "Invite id."
}
],
"responses": {
"200": {
"description": "Revoked",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "No pending invite to revoke",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/uo-link/config": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Get uo-link config + live status + ingestion stats (admin only)",
"description": "",
"responses": {
"200": {
"description": "Masked config, health and ingestion stats",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Shard"
],
"summary": "Save uo-link connection config (admin only)",
"description": "token is write-only — omit/blank it to keep the existing one. Saving (re)starts the WS ingest client.",
"responses": {
"200": {
"description": "Updated config + live status",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Validation error, or missing token while enabling",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"baseUrl": {
"type": "string"
},
"wsUrl": {
"type": "string"
},
"token": {
"type": "string"
},
"protocol": {
"type": "integer"
},
"enabled": {
"type": "boolean"
}
}
}
}
}
}
}
},
"/api/v1/admin/uo-link/towncrier": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Publish / replace a town-crier message (admin only)",
"description": "",
"responses": {
"200": {
"description": "Posted",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Rejected (over caps)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Shard unavailable",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TownCrierRequest"
}
}
}
}
}
},
"/api/v1/admin/uo-link/towncrier/{id}": {
"delete": {
"tags": [
"Admin · Shard"
],
"summary": "Remove a town-crier message (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Town-crier message id."
}
],
"responses": {
"200": {
"description": "Removed",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Unknown id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Service Unavailable"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/uo-link/stream": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Full live shard event stream incl. audit/cheat (SSE, admin only)",
"description": "",
"responses": {
"200": {
"description": "An SSE stream (Content-Type: text/event-stream)."
}
}
}
},
"/api/v1/player/account": {
"get": {
"tags": [
"Player"
],
"summary": "Get the current player account (self)",
"description": "",
"responses": {
"200": {
"description": "The player account",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PlayerAccount"
}
}
}
},
"401": {
"description": "Not authenticated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Player role required, or account not active",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/account/username": {
"patch": {
"tags": [
"Player"
],
"summary": "Change the current players username",
"description": "",
"responses": {
"200": {
"description": "Updated username (session cookie re-issued)",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"username": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "Validation error or unavailable username",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Player role required, or account not active",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Username already taken",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many changes (rate limited)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChangeUsernameRequest"
}
}
}
}
}
},
"/api/v1/player/account/password": {
"patch": {
"tags": [
"Player"
],
"summary": "Change or set the current players password",
"description": "If the account already has a password, currentPassword is required and verified. SSO-provisioned accounts with no password may set an initial one without a current password. On success the callers session is re-issued (they stay logged in) while all other sessions are revoked.",
"responses": {
"200": {
"description": "Password changed",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OkFlag"
}
}
}
},
"400": {
"description": "Validation error or wrong current password",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Player role required, or account not active",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Too many changes (rate limited)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChangePasswordRequest"
}
}
}
}
}
},
"/api/v1/player/account/totp/setup": {
"post": {
"tags": [
"Player"
],
"summary": "Begin 2FA enrollment (returns secret + QR)",
"description": "",
"responses": {
"200": {
"description": "otpauth URL and QR data to scan",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpSetup"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"409": {
"description": "Two-factor already enabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/account/totp/enable": {
"post": {
"tags": [
"Player"
],
"summary": "Enable 2FA by confirming a code",
"description": "",
"responses": {
"200": {
"description": "2FA enabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpState"
}
}
}
},
"400": {
"description": "Setup not started, or invalid code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"409": {
"description": "Two-factor already enabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpCodeRequest"
}
}
}
}
}
},
"/api/v1/player/account/totp/disable": {
"post": {
"tags": [
"Player"
],
"summary": "Disable 2FA by confirming a code",
"description": "Requires a valid current authenticator code (proves control of the authenticator); it does not take a password.",
"responses": {
"200": {
"description": "2FA disabled",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpState"
}
}
}
},
"400": {
"description": "Not enabled, or invalid code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TotpCodeRequest"
}
}
}
}
}
},
"/api/v1/player/account/identities": {
"get": {
"tags": [
"Player"
],
"summary": "List linked SSO identities (self)",
"description": "",
"responses": {
"200": {
"description": "Linked identities",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LinkedIdentity"
}
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/account/identities/{provider}": {
"delete": {
"tags": [
"Player"
],
"summary": "Unlink an SSO identity (self)",
"description": "",
"parameters": [
{
"name": "provider",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Provider id."
}
],
"responses": {
"200": {
"description": "Unlinked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnlinkedFlag"
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "No linked account for that provider",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/shard/link": {
"post": {
"tags": [
"Player · Shard"
],
"summary": "Link an in-game account with a one-time code",
"description": "The player runs [link in game to get a code, then submits it here. The server confirms it with the sidecar and mirrors the link.",
"responses": {
"200": {
"description": "Linked",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardLinkResult"
}
}
}
},
"400": {
"description": "Unknown or expired code",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Shard unavailable — retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShardLinkRequest"
}
}
}
}
}
},
"/api/v1/player/shard/account": {
"post": {
"tags": [
"Player · Shard"
],
"summary": "Create a game account (hybrid signup) and link it to the caller",
"description": "Provisions a new game account with its own username + password and auto-links it to the signed-in website user. Available only when game_account_signup is enabled and the shard accepts website signups. The password is hashed on the shard and never stored or logged by the site.",
"responses": {
"201": {
"description": "Account created and linked",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"account": {
"type": "string"
},
"linked": {
"type": "boolean"
}
}
}
}
}
},
"400": {
"description": "Validation error or rejected name/password",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Game-account signup unavailable (site or shard)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Account name already taken",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Per-IP account cap reached",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Shard unavailable — retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {}
}
},
"/api/v1/player/shard/accounts": {
"get": {
"tags": [
"Player · Shard"
],
"summary": "List the callers linked game accounts",
"description": "",
"responses": {
"200": {
"description": "Linked accounts",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardLink"
}
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/shard/roster/{account}": {
"get": {
"tags": [
"Player · Shard"
],
"summary": "Character roster for a linked account",
"description": "",
"parameters": [
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "A game account linked to the caller."
}
],
"responses": {
"200": {
"description": "Account roster",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Account not linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Shard unavailable — retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/shard/vendors/{account}": {
"get": {
"tags": [
"Player · Shard"
],
"summary": "Player vendors for a linked account",
"description": "",
"parameters": [
{
"name": "account",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "A game account linked to the caller."
}
],
"responses": {
"200": {
"description": "Vendor snapshot",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Account not linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"503": {
"description": "Shard unavailable — retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/shard/char/{serial}": {
"get": {
"tags": [
"Player · Shard"
],
"summary": "Character sheet — only for a character on the callers linked account",
"description": "",
"parameters": [
{
"name": "serial",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Mobile serial, e.g. 0x24C."
}
],
"responses": {
"200": {
"description": "Character profile",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Character not on an account linked to the caller",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Not Found"
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Shard unavailable — retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/shard/sales": {
"get": {
"tags": [
"Player · Shard"
],
"summary": "Recent player-vendor sales for the callers linked accounts",
"description": "",
"responses": {
"200": {
"description": "Vendor sales",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardVendorSale"
}
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/player/shard/houses": {
"get": {
"tags": [
"Player · Shard"
],
"summary": "The callers own houses (home status)",
"description": "Houses owned by the callers linked accounts, with decay/IDOC status. Only the callers own houses — never anyone elses.",
"responses": {
"200": {
"description": "The callers houses",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ShardHouse"
}
}
}
}
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
}
},
"components": {
"securitySchemes": {
"cookieAuth": {
"type": "apiKey",
"in": "cookie",
"name": "rg_token",
"description": "Session JWT set as an httpOnly cookie by POST /api/v1/auth/login."
},
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Access token from POST /api/v1/auth/mobile/login (or /refresh)."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"message": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Not found"
}
}
}
}
}
}
},
"ValidationError": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"errors": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"type": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "field"
}
}
},
"msg": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Invalid value"
}
}
},
"path": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "username"
}
}
},
"location": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "body"
}
}
}
}
}
}
}
}
}
}
}
}
},
"SafeUser": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 1
}
}
},
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"role": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"admin",
"editor"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "admin"
}
}
}
}
}
}
},
"LoginRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"username",
"password"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"password": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
},
"example": {
"type": "string",
"example": "super-secret"
}
}
},
"company": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Honeypot — must be empty for humans."
},
"example": {
"type": "string",
"example": ""
}
}
}
}
}
}
},
"RegisterRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"username",
"password"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"minLength": {
"type": "number",
"example": 3
},
"maxLength": {
"type": "number",
"example": 32
},
"example": {
"type": "string",
"example": "newplayer"
}
}
},
"password": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
},
"minLength": {
"type": "number",
"example": 8
},
"maxLength": {
"type": "number",
"example": 64
}
}
},
"email": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "email"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "player@example.com"
}
}
},
"company": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Honeypot — must be empty for humans."
},
"example": {
"type": "string",
"example": ""
}
}
}
}
}
}
},
"LoginResponse": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Either a session (user) or, for 2FA accounts, a TOTP challenge to complete at /login/totp."
},
"properties": {
"type": "object",
"properties": {
"user": {
"$ref": "#/components/schemas/SafeUser"
},
"totpRequired": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"challenge": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Signed challenge token for the TOTP step."
}
}
}
}
}
}
},
"TotpLoginRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"challenge",
"code"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"challenge": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Token returned by /login when totpRequired."
}
}
},
"code": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "123456"
}
}
}
}
}
}
},
"MobileLoginRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"username",
"password"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"password": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
},
"example": {
"type": "string",
"example": "super-secret"
}
}
},
"code": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "TOTP code (only when 2FA is enabled)."
},
"example": {
"type": "string",
"example": "123456"
}
}
}
}
}
}
},
"MobileTokenResponse": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"accessToken": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Short-lived bearer JWT."
}
}
},
"refreshToken": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Long-lived, revocable refresh token."
}
}
},
"expiresIn": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Access token lifetime as a duration string (zeit/ms format, e.g. \"15m\")."
},
"example": {
"type": "string",
"example": "15m"
}
}
},
"user": {
"$ref": "#/components/schemas/SafeUser"
}
}
}
}
},
"MobileRefreshRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"refreshToken"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"refreshToken": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
}
}
}
}
},
"MobileLogoutRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"refreshToken": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Revoke a single session."
}
}
},
"all": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "Revoke every session for the user."
},
"example": {
"type": "boolean",
"example": false
}
}
}
}
}
}
},
"Message": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"message": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Logged out."
}
}
}
}
}
}
},
"ContactRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"message"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"message": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 5000
},
"example": {
"type": "string",
"example": "When does the shard launch?"
}
}
},
"email": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "email"
},
"example": {
"type": "string",
"example": "player@example.com"
}
}
},
"name": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 100
},
"example": {
"type": "string",
"example": "Lord British"
}
}
}
}
}
}
},
"Provider": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "google"
}
}
},
"name": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Google"
}
}
},
"icon": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Icon hint — the provider kind ('google' | 'discord' | 'oidc' | 'oauth2')."
},
"example": {
"type": "string",
"example": "google"
}
}
},
"loginUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Relative URL to begin the redirect flow."
},
"example": {
"type": "string",
"example": "/api/v1/auth/sso/google/start"
}
}
},
"priority": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"description": {
"type": "string",
"example": "Sort order (ascending)."
},
"example": {
"type": "number",
"example": 1
}
}
}
}
}
}
},
"ProviderConfig": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "okta"
}
}
},
"kind": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"google",
"discord",
"oidc",
"oauth2"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "oidc"
}
}
},
"name": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Okta"
}
}
},
"enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"clientId": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"hasSecret": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "Whether a client secret is stored (the secret itself is never returned)."
},
"example": {
"type": "boolean",
"example": true
}
}
},
"authorizeUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "uri"
}
}
},
"tokenUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "uri"
}
}
},
"userinfoUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "uri"
}
}
},
"scopes": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "openid email profile"
}
}
},
"priority": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 10
}
}
},
"builtin": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "True for the fixed google/discord providers."
},
"example": {
"type": "boolean",
"example": false
}
}
},
"health": {
"$ref": "#/components/schemas/ProviderHealth"
}
}
}
}
},
"ProviderHealth": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Config-completeness check that gates whether a provider is offered to end users."
},
"properties": {
"type": "object",
"properties": {
"valid": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"missing": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"description": {
"type": "string",
"example": "Names of required config fields that are still missing."
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"example": {
"type": "array",
"example": [],
"items": {}
}
}
}
}
}
}
},
"ProviderCreateRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"id",
"kind",
"name"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"pattern": {
"type": "string",
"example": "^[a-z0-9-]+$"
},
"example": {
"type": "string",
"example": "okta"
}
}
},
"kind": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"oidc",
"oauth2"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "oidc"
}
}
},
"name": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 80
},
"example": {
"type": "string",
"example": "Okta"
}
}
},
"enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"clientId": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"secret": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
}
}
},
"authorizeUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "uri"
}
}
},
"tokenUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "uri"
}
}
},
"userinfoUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "uri"
}
}
},
"scopes": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 500
},
"example": {
"type": "string",
"example": "openid email profile"
}
}
},
"priority": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 10
}
}
}
}
}
}
},
"Post": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 12
}
}
},
"category": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "news"
}
}
},
"title": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Server maintenance this weekend"
}
}
},
"slug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "server-maintenance-this-weekend"
}
}
},
"excerpt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"body": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"image_url": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "/uploads/1700000000-abcd.png"
}
}
},
"published": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"author_id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 1
}
}
},
"created_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
},
"updated_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
},
"published_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
},
"nullable": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"PostCreateRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"category",
"title"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"category": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "news"
}
}
},
"title": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 200
},
"example": {
"type": "string",
"example": "Server maintenance this weekend"
}
}
},
"body": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"image_url": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "Required for the screenshots category."
}
}
},
"published": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": false
}
}
}
}
}
}
},
"PublishRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"published"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"published": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"UploadResponse": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"url": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "/uploads/1700000000-abcd.png"
}
}
}
}
}
}
},
"WikiPage": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 3
}
}
},
"slug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "getting-started"
}
}
},
"title": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Getting Started"
}
}
},
"excerpt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"body": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"category_id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 2
}
}
},
"published": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"tags": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"example": {
"type": "array",
"example": [
"newbie",
"guide"
],
"items": {
"type": "string"
}
}
}
},
"created_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
},
"updated_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
}
}
}
}
},
"WikiPageCreateRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"slug",
"title"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"slug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"pattern": {
"type": "string",
"example": "^[a-z0-9-]+$"
},
"example": {
"type": "string",
"example": "getting-started"
}
}
},
"title": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 200
},
"example": {
"type": "string",
"example": "Getting Started"
}
}
},
"excerpt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 400
}
}
},
"body": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"category_id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"published": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": false
}
}
},
"tags": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
}
}
}
}
}
}
},
"WikiCategory": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 2
}
}
},
"slug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "guides"
}
}
},
"title": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Guides"
}
}
},
"description": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
}
}
},
"sort_order": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 0
}
}
}
}
}
}
},
"WikiCategoryCreateRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"slug",
"title"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"slug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"pattern": {
"type": "string",
"example": "^[a-z0-9-]+$"
},
"example": {
"type": "string",
"example": "guides"
}
}
},
"title": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 200
},
"example": {
"type": "string",
"example": "Guides"
}
}
},
"description": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 400
}
}
},
"sort_order": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 0
}
}
}
}
}
}
},
"User": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 1
}
}
},
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"role": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"admin",
"editor",
"moderator",
"player"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"status": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"active",
"disabled",
"banned",
"pending"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "active"
}
}
},
"email": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "email"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"email_verified": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": false
}
}
},
"totp_enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"last_login_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"created_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
}
}
}
}
},
"UserCreateRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"username",
"password"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"minLength": {
"type": "number",
"example": 3
},
"maxLength": {
"type": "number",
"example": 32
},
"example": {
"type": "string",
"example": "editor1"
}
}
},
"password": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
},
"minLength": {
"type": "number",
"example": 8
},
"maxLength": {
"type": "number",
"example": 64
}
}
},
"role": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"admin",
"editor",
"moderator",
"player"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "editor"
}
}
},
"status": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"active",
"disabled",
"banned",
"pending"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "active"
}
}
},
"email": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "email"
},
"nullable": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"ChangeUsernameRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"username"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"minLength": {
"type": "number",
"example": 3
},
"maxLength": {
"type": "number",
"example": 32
},
"example": {
"type": "string",
"example": "newname"
}
}
}
}
}
}
},
"ChangePasswordRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"newPassword"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"newPassword": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
},
"minLength": {
"type": "number",
"example": 8
},
"maxLength": {
"type": "number",
"example": 64
}
}
},
"currentPassword": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "password"
},
"description": {
"type": "string",
"example": "Required when the account already has a password. Omit only for an SSO-provisioned account setting its first password."
}
}
}
}
}
}
},
"PlayerAccount": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Self-service player account (GET /player/account)."
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 42
}
}
},
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "newplayer"
}
}
},
"role": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"player"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "player"
}
}
},
"email": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "email"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "player@example.com"
}
}
},
"status": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"active",
"disabled",
"banned",
"pending"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "active"
}
}
},
"totp_enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": false
}
}
},
"has_password": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "False for an SSO-provisioned account that has not set a password yet."
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"OkFlag": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"ok": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"TotpCodeRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"code"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"code": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "123456"
}
}
}
}
}
}
},
"SiteModeRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"mode"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"mode": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"live",
"maintenance"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "live"
}
}
}
}
}
}
},
"UnbanRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"ip"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"ip": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "203.0.113.5"
}
}
}
}
}
}
},
"AccountStatus": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Self-service account security status (GET /admin/account)."
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 1
}
}
},
"username": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"role": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"admin",
"editor"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "admin"
}
}
},
"totp_enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"TotpSetup": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Enrollment material returned by POST /account/totp/setup."
},
"properties": {
"type": "object",
"properties": {
"otpauthUrl": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "otpauth://totp/Runic Gateway:admin?secret=..."
}
}
},
"qr": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "QR code as a data: URL."
},
"example": {
"type": "string",
"example": "data:image/png;base64,iVBORw0KGgo..."
}
}
}
}
}
}
},
"TotpState": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Result of enabling/disabling 2FA."
},
"properties": {
"type": "object",
"properties": {
"totp_enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"LinkedIdentity": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"provider": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "google"
}
}
},
"email": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "email"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "user@example.com"
}
}
},
"linked_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
}
}
}
}
},
"SiteModeState": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Result of PUT /admin/site-mode."
},
"properties": {
"type": "object",
"properties": {
"site_mode": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"live",
"maintenance"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "maintenance"
}
}
},
"changed_at": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
},
"changed_by": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "admin"
}
}
}
}
}
}
},
"PublicStatus": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Public site status (GET /public/status)."
},
"properties": {
"type": "object",
"properties": {
"mode": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"enum": {
"type": "array",
"example": [
"live",
"maintenance"
],
"items": {
"type": "string"
}
},
"example": {
"type": "string",
"example": "live"
}
}
},
"status_message": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": ""
}
}
}
}
}
}
},
"DeletedId": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 12
}
}
}
}
}
}
},
"DeletedSlug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"slug": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "getting-started"
}
}
}
}
}
}
},
"DeletedFlag": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"deleted": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"UnlinkedFlag": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"unlinked": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"UnbanResult": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"ip": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "203.0.113.5"
}
}
},
"removed": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "Whether the IP had an entry that was cleared."
},
"example": {
"type": "boolean",
"example": true
}
}
}
}
}
}
},
"ShardStatus": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "Public shard status (GET /public/shard/status)."
},
"properties": {
"type": "object",
"properties": {
"enabled": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"status": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "connected"
},
"description": {
"type": "string",
"example": "connected | reconnecting | disconnected | error"
}
}
},
"pluginConnected": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"description": {
"type": "string",
"example": "Is the shard link up right now?"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"lastEventAt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"onlineCount": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 12
}
}
},
"economy": {
"$ref": "#/components/schemas/ShardEconomyPoint"
}
}
}
}
},
"ShardEvent": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "A logged shard event."
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 4821
}
}
},
"kind": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "vendor.sale"
}
}
},
"t": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"description": {
"type": "string",
"example": "Event time, epoch ms."
},
"example": {
"type": "number",
"example": 1783720195626
}
}
},
"bootId": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "boot-abc123"
}
}
},
"payload": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"additionalProperties": {
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "The full event object."
}
}
},
"createdAt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
}
}
}
}
},
"ShardEconomyPoint": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"nullable": {
"type": "boolean",
"example": true
},
"description": {
"type": "string",
"example": "One gold-supply sample."
},
"properties": {
"type": "object",
"properties": {
"accounts": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 240
}
}
},
"gold": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 1028983421
}
}
},
"t": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"description": {
"type": "string",
"example": "Sample time, epoch ms."
},
"example": {
"type": "number",
"example": 1783720000000
}
}
}
}
}
}
},
"ShardOnlinePlayer": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "A LINKED player online now (only accounts linked to a website user are listed)."
},
"properties": {
"type": "object",
"properties": {
"serial": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "0x24C"
}
}
},
"name": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Darrow"
}
}
},
"map": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "Trammel"
}
}
},
"x": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 1402
}
}
},
"y": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 1604
}
}
},
"z": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 0
}
}
}
}
}
}
},
"ShardVendorSale": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "A player-vendor sale (visible only to the linked owner)."
},
"properties": {
"type": "object",
"properties": {
"t": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"description": {
"type": "string",
"example": "Sale time, epoch ms."
},
"example": {
"type": "number",
"example": 1783720195626
}
}
},
"itemType": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "Longsword"
}
}
},
"amount": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 1
}
}
},
"price": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 100
}
}
},
"commission": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "number",
"example": 5
}
}
},
"ownerAcct": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "whitlocktech"
}
}
}
}
}
}
},
"ShardHouse": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "A house at its current decay stage."
},
"properties": {
"type": "object",
"properties": {
"serial": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "0x4004705F"
}
}
},
"stage": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "IDOC"
}
}
},
"map": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "Trammel"
}
}
},
"x": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"y": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"z": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"region": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"name": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "An Unnamed House"
}
}
},
"ownerSerial": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"ownerAcct": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"builtOn": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"lastRefreshed": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
},
"nullable": {
"type": "boolean",
"example": true
}
}
},
"isIdoc": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"updatedAt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
}
}
}
}
},
"ShardLinkRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"code"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"code": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"description": {
"type": "string",
"example": "The one-time code shown by [link in game."
},
"example": {
"type": "string",
"example": "AB12CD"
}
}
}
}
}
}
},
"ShardLinkResult": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"properties": {
"type": "object",
"properties": {
"linked": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "boolean"
},
"example": {
"type": "boolean",
"example": true
}
}
},
"account": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "whitlocktech"
}
}
}
}
}
}
},
"ShardLink": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"description": {
"type": "string",
"example": "A linked in-game account (GET /player/shard/accounts)."
},
"properties": {
"type": "object",
"properties": {
"account": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"example": {
"type": "string",
"example": "whitlocktech"
}
}
},
"userId": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"example": {
"type": "number",
"example": 42
}
}
},
"charName": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"nullable": {
"type": "boolean",
"example": true
},
"example": {
"type": "string",
"example": "Darrow"
}
}
},
"linkedAt": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"format": {
"type": "string",
"example": "date-time"
}
}
}
}
}
}
},
"TownCrierRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"id",
"lines"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 64
},
"description": {
"type": "string",
"example": "Re-posting the same id replaces the prior entry."
},
"example": {
"type": "string",
"example": "news-42"
}
}
},
"lines": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 200
}
}
},
"example": {
"type": "array",
"example": [
"Hear ye!",
"Market tax is now 5%."
],
"items": {
"type": "string"
}
}
}
},
"durationSec": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"minimum": {
"type": "number",
"example": 1
},
"maximum": {
"type": "number",
"example": 86400
},
"example": {
"type": "number",
"example": 3600
}
}
}
}
}
}
}
}
}
}