{ "openapi": "3.0.0", "info": { "title": "UOMysticmoon API", "version": "1.0.0", "description": "REST API for the UOMysticmoon website, wiki and admin panel — a private Ultima Online shard.\n\n### Authentication\n- **Web / admin panel** uses an httpOnly session cookie (`uomm_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": "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 · 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/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/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/char/{serial}": { "get": { "tags": [ "Public · Shard" ], "summary": "Live character sheet by serial (cached; degrades on shard restart)", "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": "Invalid serial", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "404": { "description": "Character not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "500": { "description": "Internal Server Error" }, "502": { "description": "Bad Gateway" }, "503": { "description": "Shard restarting — retry", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/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/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/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": [] } ] } }, "/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 player’s 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 player’s 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 caller’s 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": [] } ] } } }, "components": { "securitySchemes": { "cookieAuth": { "type": "apiKey", "in": "cookie", "name": "uomm_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/UOMysticmoon: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 } } } } } } }, "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" } } } } } } } } } }