openapi: 3.1.0 info: title: Minecraft Account Manager API version: 0.1.0 description: Supported application API contract. Root openapi.yaml is canonical and publicly available at /openapi.yaml; no interactive UI. Includes identity, read-only suggestions and Velocity integrations. Framework NextAuth routes, browser magic-link flows and server actions are excluded, as are infrastructure health probes and unknown-route fallbacks. Admin APIs accept session OR Keycloak bearer; any Authorization header disables session fallback. Machine tokens require RS256, exact configured issuer, portal audience, expiry, subject and configured-client administrator role. Obtain client_credentials tokens from /protocol/openid-connect/token using a separately authorized service-account client; see docs/admin-api-authentication.md for safe in-memory usage. Machine credentials do not grant Velocity access. servers: - url: https://portal.somc.club description: Production portal. - url: http://localhost:3000 description: Local development tags: - name: Identity description: Administrator identity checks. - name: Suggestions description: Read-only configured Discord forum. - name: Velocity description: Proxy integrations using a distinct shared secret. paths: /api/admin/whoami: get: tags: - Identity operationId: getAdminIdentity summary: Verify administrator identity description: Returns only safe identity fields. No database or Discord access. Supplied Authorization exclusively selects bearer verification. HEAD is provided by Next.js with the same checks/status/headers and no body; other unsupported methods receive framework empty 405, OPTIONS is framework-generated. security: - AdminSession: [] - AdminBearer: [] responses: "200": $ref: "#/components/responses/AdminIdentity" "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "503": $ref: "#/components/responses/AdminAuthUnavailable" head: tags: - Identity operationId: headAdminIdentity summary: Check administrator identity headers description: Next.js delegates to GET, including authentication; HTTP HEAD has no response body. security: - AdminSession: [] - AdminBearer: [] responses: "200": $ref: "#/components/responses/ReadHead" "401": $ref: "#/components/responses/AdminUnauthorizedHead" "403": $ref: "#/components/responses/AdminForbiddenHead" "503": $ref: "#/components/responses/AdminAuthUnavailableHead" /api/suggestions: get: tags: - Suggestions operationId: listSuggestions summary: List suggestions description: "Active posts are newest-created first; archives newest-archived first. Unknown, empty or repeated query parameters are rejected. Threads may move between lists; this is not a snapshot. Authorization is checked before every cached/fresh read. Process-local Discord cache: 30 seconds, 200 entries, eight concurrent requests; responses remain no-store." security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/limit_1" - $ref: "#/components/parameters/status_2" - $ref: "#/components/parameters/cursor_3" responses: "200": $ref: "#/components/responses/Suggestions" "400": $ref: "#/components/responses/InvalidSuggestionRequest" "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "404": $ref: "#/components/responses/SuggestionNotFound" "503": $ref: "#/components/responses/SuggestionsUnavailable" head: tags: - Suggestions operationId: listSuggestionsHead summary: Read headers without a body description: Implicit Next.js HEAD runs GET with the same authentication, validation and upstream/cache behavior, but suppresses the response body. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/limit_1" - $ref: "#/components/parameters/status_2" - $ref: "#/components/parameters/cursor_3" responses: "200": $ref: "#/components/responses/ReadHead" "400": $ref: "#/components/responses/InvalidSuggestionRequestHead" "401": $ref: "#/components/responses/AdminUnauthorizedHead" "403": $ref: "#/components/responses/AdminForbiddenHead" "404": $ref: "#/components/responses/SuggestionNotFoundHead" "503": $ref: "#/components/responses/SuggestionsUnavailableHead" post: tags: - Suggestions operationId: listSuggestionsPostRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" put: tags: - Suggestions operationId: listSuggestionsPutRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" patch: tags: - Suggestions operationId: listSuggestionsPatchRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" delete: tags: - Suggestions operationId: listSuggestionsDeleteRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" options: tags: - Suggestions operationId: listSuggestionsOptionsRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" /api/suggestions/{id}: get: tags: - Suggestions operationId: getSuggestion summary: Get suggestion description: "Metadata and original starter, or null when deleted. Query parameters are ignored by this detail route. Unrelated forum threads are inaccessible. Authorization is checked before every cached/fresh read. Process-local Discord cache: 30 seconds, 200 entries, eight concurrent requests; responses remain no-store." security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "200": $ref: "#/components/responses/SuggestionDetail" "400": $ref: "#/components/responses/InvalidSuggestionRequest" "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "404": $ref: "#/components/responses/SuggestionNotFound" "503": $ref: "#/components/responses/SuggestionsUnavailable" head: tags: - Suggestions operationId: getSuggestionHead summary: Read headers without a body description: Implicit Next.js HEAD runs GET with the same authentication, validation and upstream/cache behavior, but suppresses the response body. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "200": $ref: "#/components/responses/ReadHead" "400": $ref: "#/components/responses/InvalidSuggestionRequestHead" "401": $ref: "#/components/responses/AdminUnauthorizedHead" "403": $ref: "#/components/responses/AdminForbiddenHead" "404": $ref: "#/components/responses/SuggestionNotFoundHead" "503": $ref: "#/components/responses/SuggestionsUnavailableHead" post: tags: - Suggestions operationId: getSuggestionPostRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" put: tags: - Suggestions operationId: getSuggestionPutRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" patch: tags: - Suggestions operationId: getSuggestionPatchRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" delete: tags: - Suggestions operationId: getSuggestionDeleteRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" options: tags: - Suggestions operationId: getSuggestionOptionsRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" /api/suggestions/{id}/messages: get: tags: - Suggestions operationId: listSuggestionMessages summary: List suggestion messages description: "Newest-first discussion, including the starter if reached. A final empty page is possible. Unknown, empty or repeated query parameters are rejected. Authorization is checked before every cached/fresh read. Process-local Discord cache: 30 seconds, 200 entries, eight concurrent requests; responses remain no-store." security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" - $ref: "#/components/parameters/limit_1" - $ref: "#/components/parameters/cursor_5" responses: "200": $ref: "#/components/responses/SuggestionMessages" "400": $ref: "#/components/responses/InvalidSuggestionRequest" "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "404": $ref: "#/components/responses/SuggestionNotFound" "503": $ref: "#/components/responses/SuggestionsUnavailable" head: tags: - Suggestions operationId: listSuggestionMessagesHead summary: Read headers without a body description: Implicit Next.js HEAD runs GET with the same authentication, validation and upstream/cache behavior, but suppresses the response body. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" - $ref: "#/components/parameters/limit_1" - $ref: "#/components/parameters/cursor_5" responses: "200": $ref: "#/components/responses/ReadHead" "400": $ref: "#/components/responses/InvalidSuggestionRequestHead" "401": $ref: "#/components/responses/AdminUnauthorizedHead" "403": $ref: "#/components/responses/AdminForbiddenHead" "404": $ref: "#/components/responses/SuggestionNotFoundHead" "503": $ref: "#/components/responses/SuggestionsUnavailableHead" post: tags: - Suggestions operationId: listSuggestionMessagesPostRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" put: tags: - Suggestions operationId: listSuggestionMessagesPutRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" patch: tags: - Suggestions operationId: listSuggestionMessagesPatchRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" delete: tags: - Suggestions operationId: listSuggestionMessagesDeleteRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" options: tags: - Suggestions operationId: listSuggestionMessagesOptionsRejected summary: Reject unsupported method description: Read-only endpoint. Authentication is checked first; authorized requests return 405, without evaluating query/body. No mutation is supported. security: - AdminSession: [] - AdminBearer: [] parameters: - $ref: "#/components/parameters/id_4" responses: "401": $ref: "#/components/responses/AdminUnauthorized" "403": $ref: "#/components/responses/AdminForbidden" "405": $ref: "#/components/responses/SuggestionsReadOnly" "503": $ref: "#/components/responses/AdminAuthUnavailable" /api/velocity/access: post: tags: - Velocity operationId: decideVelocityAccess summary: Decide Minecraft admission description: "Uses a separately provisioned per-server shared bearer secret (hashed in plugin credentials), NOT a Keycloak machine JWT or browser session. The Bearer prefix is case-sensitive. Requires fresh timestamp and unique request UUID; replay retention is five minutes. Unknown body properties are stripped. GET/PUT/PATCH/DELETE return RFC 9457 405 with Allow: POST; implicit HEAD uses GET without a body. OPTIONS is framework-generated." security: - VelocitySecret: [] requestBody: required: true description: JSON payload; all documented fields required. Example timestamp is illustrative and must be replaced with current time. content: application/json: schema: $ref: "#/components/schemas/VelocityAccessRequest" examples: representative: $ref: "#/components/examples/AccessRequest" responses: "200": $ref: "#/components/responses/VelocityDecision" "400": $ref: "#/components/responses/InvalidAccessRequest" "401": $ref: "#/components/responses/VelocityAccessUnauthorized" "409": $ref: "#/components/responses/AccessReplay" "415": $ref: "#/components/responses/VelocityUnsupportedMedia" "503": $ref: "#/components/responses/VelocityAccessUnavailable" get: tags: - Velocity operationId: rejectVelocityaccessget summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" put: tags: - Velocity operationId: rejectVelocityaccessput summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" patch: tags: - Velocity operationId: rejectVelocityaccesspatch summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" delete: tags: - Velocity operationId: rejectVelocityaccessdelete summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" head: tags: - Velocity operationId: rejectVelocityaccesshead summary: Reject unsupported method description: Next.js delegates HEAD to the GET rejection and suppresses its body. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowedHead" /api/velocity/connection: post: tags: - Velocity operationId: recordVelocityConnection summary: Record a confirmed connection description: "Uses a separately provisioned per-server shared bearer secret (hashed in plugin credentials), NOT a Keycloak machine JWT or browser session. The Bearer prefix is case-sensitive. Requires fresh timestamp and unique request UUID; replay retention is five minutes. Unknown body properties are stripped. GET/PUT/PATCH/DELETE return RFC 9457 405 with Allow: POST; implicit HEAD uses GET without a body. OPTIONS is framework-generated." security: - VelocitySecret: [] requestBody: required: true description: JSON payload; all documented fields required. Example timestamp is illustrative and must be replaced with current time. content: application/json: schema: $ref: "#/components/schemas/VelocityConnectionRequest" examples: representative: $ref: "#/components/examples/ConnectionRequest" responses: "204": $ref: "#/components/responses/ConnectionRecorded" "400": $ref: "#/components/responses/InvalidConnectionRequest" "401": $ref: "#/components/responses/VelocityConnectionUnauthorized" "404": $ref: "#/components/responses/UnknownMinecraftAccount" "409": $ref: "#/components/responses/ConnectionReplay" "415": $ref: "#/components/responses/VelocityUnsupportedMedia" "500": $ref: "#/components/responses/FrameworkConnectionFailure" "503": $ref: "#/components/responses/VelocityConnectionUnavailable" get: tags: - Velocity operationId: rejectVelocityconnectionget summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" put: tags: - Velocity operationId: rejectVelocityconnectionput summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" patch: tags: - Velocity operationId: rejectVelocityconnectionpatch summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" delete: tags: - Velocity operationId: rejectVelocityconnectiondelete summary: Reject unsupported method description: Always returns 405 without accessing credentials or the database. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowed" head: tags: - Velocity operationId: rejectVelocityconnectionhead summary: Reject unsupported method description: Next.js delegates HEAD to the GET rejection and suppresses its body. security: [] responses: "405": $ref: "#/components/responses/VelocityMethodNotAllowedHead" components: securitySchemes: AdminSession: type: apiKey in: cookie name: __Secure-next-auth.session-token description: Existing NextAuth administrator session. Local HTTP uses next-auth.session-token instead. Player sessions do not qualify. Used only when Authorization is absent. AdminBearer: type: http scheme: bearer bearerFormat: JWT description: Keycloak RS256 access token with configured portal aud and resource_access[portal-client].roles permission. Never send a client secret here. VelocitySecret: type: http scheme: bearer description: Separate per-server shared secret, NOT a JWT. Send the exact case-sensitive Bearer prefix. No session or machine-token alternative. schemas: Snowflake: type: string pattern: ^[1-9]\d{16,19}$ description: Discord snowflake as a string, never a JSON number. Examples are synthetic. Problem: type: object required: - type - title - status properties: type: type: string format: uri title: type: string status: type: integer minimum: 100 maximum: 599 detail: type: string instance: type: string extensions: type: object additionalProperties: true properties: issues: type: array items: type: object required: - path - message - code properties: path: type: string message: type: string code: type: string description: RFC 9457. Validation issues are nested in extensions.issues, not at the top level. Identity: oneOf: - type: object required: - authenticationMethod - subject - name - email properties: authenticationMethod: const: bearer subject: type: string minLength: 1 name: type: "null" email: type: "null" - type: object required: - authenticationMethod - subject - name - email properties: authenticationMethod: const: session subject: type: "null" name: type: - string - "null" email: type: - string - "null" Tag: type: object required: - id - name properties: id: $ref: "#/components/schemas/Snowflake" name: type: string Suggestion: type: object required: - id - title - authorId - createdAt - archived - locked - tags - messageCount - discordUrl properties: id: $ref: "#/components/schemas/Snowflake" title: type: string authorId: $ref: "#/components/schemas/Snowflake" createdAt: type: string format: date-time archived: type: boolean locked: type: boolean tags: type: array items: $ref: "#/components/schemas/Tag" messageCount: type: integer description: Approximate Discord message count, not votes. discordUrl: type: string format: uri Message: type: object required: - id - author - content - createdAt - editedAt - reactions - discordUrl properties: id: $ref: "#/components/schemas/Snowflake" author: type: object required: - id - name properties: id: $ref: "#/components/schemas/Snowflake" name: type: string content: type: string description: Literal text; may be empty due to Message Content intent. No rendered Markdown, attachments, or embeds. createdAt: type: string format: date-time editedAt: type: - string - "null" format: date-time reactions: type: array items: type: object required: - emoji - count properties: emoji: type: string count: type: integer discordUrl: type: string format: uri SuggestionDetail: allOf: - $ref: "#/components/schemas/Suggestion" - type: object required: - originalPost properties: originalPost: anyOf: - $ref: "#/components/schemas/Message" - type: "null" description: Null if the starter was deleted. SuggestionPage: type: object required: - items - nextCursor properties: items: type: array items: $ref: "#/components/schemas/Suggestion" nextCursor: type: - string - "null" description: Null at end; active thread ID or archived UTC timestamp with up to six fractional digits. Treat as opaque; URL-encode and keep the same status. MessagePage: type: object required: - items - nextCursor properties: items: type: array items: $ref: "#/components/schemas/Message" nextCursor: type: - string - "null" description: Message ID or null. A full last page can yield a cursor followed by an empty page. VelocityConnectionRequest: type: object required: - requestId - serverId - minecraftUuid - username - occurredAt properties: requestId: type: string format: uuid serverId: type: string minLength: 1 maxLength: 100 minecraftUuid: type: string pattern: ^[0-9a-fA-F]{32}$ username: type: string pattern: ^[A-Za-z0-9_]{3,16}$ occurredAt: type: string format: date-time description: RFC 3339 timestamp; UTC Z recommended, numeric offsets accepted. Must be within 45 seconds of server time. VelocityAccessRequest: allOf: - $ref: "#/components/schemas/VelocityConnectionRequest" - type: object required: - ipAddress properties: ipAddress: anyOf: - type: string format: ipv4 - type: string format: ipv6 VelocityDecision: type: object required: - allowed - message properties: allowed: type: boolean message: type: string examples: MachineIdentity: summary: Synthetic, non-live example value: authenticationMethod: bearer subject: example-machine-subject name: null email: null BrowserIdentity: summary: Synthetic, non-live example value: authenticationMethod: session subject: null name: Example operator email: null Suggestions: summary: Synthetic, non-live example value: items: - id: "100000000000000009" title: Add a community garden authorId: "100000000000000003" createdAt: 2026-09-10T00:00:00Z archived: false locked: false tags: - id: "100000000000000004" name: Idea messageCount: 2 discordUrl: https://discord.com/channels/100000000000000001/100000000000000009 nextCursor: null DeletedStarter: summary: Synthetic, non-live example value: id: "100000000000000009" title: Add a community garden authorId: "100000000000000003" createdAt: 2026-09-10T00:00:00Z archived: false locked: false tags: - id: "100000000000000004" name: Idea messageCount: 2 discordUrl: https://discord.com/channels/100000000000000001/100000000000000009 originalPost: null Messages: summary: Synthetic, non-live example value: items: - id: "100000000000000010" author: id: "100000000000000003" name: Example player content: A shared garden would be fun. createdAt: 2026-09-10T00:00:00Z editedAt: null reactions: - emoji: 👍 count: 2 discordUrl: https://discord.com/channels/100000000000000001/100000000000000009/100000000000000010 nextCursor: "100000000000000010" ConnectionRequest: summary: Synthetic, non-live example value: requestId: 00000000-0000-4000-8000-000000000001 serverId: example-proxy minecraftUuid: "00000000000040008000000000000001" username: ExamplePlayer occurredAt: 2026-09-10T00:00:00Z AccessRequest: summary: Synthetic, non-live example value: requestId: 00000000-0000-4000-8000-000000000001 serverId: example-proxy minecraftUuid: "00000000000040008000000000000001" username: ExamplePlayer occurredAt: 2026-09-10T00:00:00Z ipAddress: 192.0.2.10 Allowed: summary: Synthetic, non-live example value: allowed: true message: Account approved. Denied: summary: Synthetic, non-live example value: allowed: false message: Please register before joining. Unauthorized: summary: Synthetic, non-live example value: type: urn:error:unauthorized title: Unauthorized status: 401 detail: Administrator authentication is required. instance: /api/admin/whoami responses: AdminIdentity: description: Successful read. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/json: schema: $ref: "#/components/schemas/Identity" examples: representative: $ref: "#/components/examples/MachineIdentity" browser: $ref: "#/components/examples/BrowserIdentity" AdminUnauthorized: description: Missing session or invalid supplied credentials; no redirect and no session fallback. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store WWW-Authenticate: description: Bearer challenge. schema: type: string const: Bearer realm="admin-api" content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 401 type: enum: - urn:error:unauthorized examples: unauthorized: $ref: "#/components/examples/Unauthorized" AdminForbidden: description: Authenticated identity lacks the required administrator permission. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 403 type: enum: - urn:error:forbidden AdminAuthUnavailable: description: Authentication configuration, session service, or JWKS is unavailable. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 503 type: enum: - urn:error:admin-auth-unavailable ReadHead: description: Successful read. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store AdminUnauthorizedHead: description: Missing session or invalid supplied credentials; no redirect and no session fallback. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store WWW-Authenticate: description: Bearer challenge. schema: type: string const: Bearer realm="admin-api" AdminForbiddenHead: description: Authenticated identity lacks the required administrator permission. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store AdminAuthUnavailableHead: description: Authentication configuration, session service, or JWKS is unavailable. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store Suggestions: description: Successful read. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/json: schema: $ref: "#/components/schemas/SuggestionPage" examples: representative: $ref: "#/components/examples/Suggestions" InvalidSuggestionRequest: description: Invalid ID, status, cursor, limit or query parameters. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 400 type: enum: - urn:error:invalid-request SuggestionNotFound: description: Suggestion/message missing, deleted or outside the configured forum. Forum configuration failures instead use 503. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 404 type: enum: - urn:error:suggestion-not-found SuggestionsUnavailable: description: Authentication unavailable, forum unconfigured, Discord unavailable, rate limited or busy. Retry-After is present only for rate limiting/capacity; seconds, no automatic retries. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store Retry-After: description: Optional; positive whole seconds for discord-rate-limited or discord-busy. schema: type: integer minimum: 1 content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 503 type: enum: - urn:error:admin-auth-unavailable - urn:error:suggestions-not-configured - urn:error:discord-unavailable - urn:error:discord-rate-limited - urn:error:discord-busy InvalidSuggestionRequestHead: description: Invalid ID, status, cursor, limit or query parameters. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store SuggestionNotFoundHead: description: Suggestion/message missing, deleted or outside the configured forum. Forum configuration failures instead use 503. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store SuggestionsUnavailableHead: description: Authentication unavailable, forum unconfigured, Discord unavailable, rate limited or busy. Retry-After is present only for rate limiting/capacity; seconds, no automatic retries. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store Retry-After: description: Optional; positive whole seconds for discord-rate-limited or discord-busy. schema: type: integer minimum: 1 SuggestionsReadOnly: description: Suggestions are read-only. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store Allow: description: Supported reads. schema: type: string const: GET, HEAD content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 405 type: enum: - urn:error:method-not-allowed SuggestionDetail: description: Successful read. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/json: schema: $ref: "#/components/schemas/SuggestionDetail" examples: representative: $ref: "#/components/examples/DeletedStarter" SuggestionMessages: description: Successful read. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/json: schema: $ref: "#/components/schemas/MessagePage" examples: representative: $ref: "#/components/examples/Messages" VelocityDecision: description: Admission decision. Policy denial is still HTTP 200, with allowed=false and a user-facing message. content: application/json: schema: $ref: "#/components/schemas/VelocityDecision" examples: allowed: $ref: "#/components/examples/Allowed" denied: $ref: "#/components/examples/Denied" InvalidAccessRequest: description: Malformed JSON or invalid body. Includes extensions.issues. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 400 type: enum: - urn:error:invalid-velocity-access-request VelocityAccessUnauthorized: description: Missing, invalid or revoked shared server secret, or timestamp outside ±45 seconds. No admin bearer challenge. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 401 type: enum: - urn:error:unauthorized - urn:error:expired-velocity-access-request AccessReplay: description: Request ID already processed. Replay IDs are shared by both Velocity endpoints; use a fresh UUID for each request. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 409 type: enum: - urn:error:replayed-velocity-access-request VelocityUnsupportedMedia: description: Content-Type must be application/json (parameters permitted). Credential presence is checked first. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 415 type: enum: - urn:error:unsupported-media-type VelocityAccessUnavailable: description: Access decision could not be completed; unexpected failures are sanitized. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 503 type: enum: - urn:error:service-unavailable VelocityMethodNotAllowed: description: Only POST is supported; method rejection happens without credential verification. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store Allow: description: Supported method. schema: type: string const: POST content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 405 type: enum: - urn:error:method-not-allowed VelocityMethodNotAllowedHead: description: Only POST is supported; method rejection happens without credential verification. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store Allow: description: Supported method. schema: type: string const: POST ConnectionRecorded: description: Connection recorded; no body. InvalidConnectionRequest: description: Malformed JSON or invalid body. Includes extensions.issues. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 400 type: enum: - urn:error:invalid-velocity-connection-request VelocityConnectionUnauthorized: description: Missing, invalid or revoked shared server secret, or timestamp outside ±45 seconds. No admin bearer challenge. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 401 type: enum: - urn:error:unauthorized - urn:error:expired-velocity-connection-request UnknownMinecraftAccount: description: Account no longer registered. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 404 type: enum: - urn:error:unknown-minecraft-account ConnectionReplay: description: Request ID already processed. Replay IDs are shared by both Velocity endpoints; use a fresh UUID for each request. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 409 type: enum: - urn:error:replayed-velocity-connection-request FrameworkConnectionFailure: description: Unhandled credential lookup failure before the transaction catch; framework-owned response, no stable JSON contract. VelocityConnectionUnavailable: description: Transaction failure. Credential database lookup errors occur before the handler catch and may produce a framework 500, not a Problem Details response. headers: Cache-Control: description: Do not cache this response. schema: type: string const: no-store content: application/problem+json: schema: allOf: - $ref: "#/components/schemas/Problem" - type: object properties: status: const: 503 type: enum: - urn:error:service-unavailable parameters: limit_1: name: limit in: query description: One decimal integer (1–3 digits), 1–100; defaults to 25. Empty and repeated parameters are rejected. schema: type: integer minimum: 1 maximum: 100 default: 25 status_2: name: status in: query description: Select active or public archived posts. Keep unchanged when following a cursor. schema: type: string enum: - active - archived default: active cursor_3: name: cursor in: query description: "Opaque nextCursor: active snowflake or archived UTC Z timestamp (0–6 fractional digits). URL-encode it." schema: type: string minLength: 1 id_4: name: id in: path required: true description: Thread in the configured forum only. schema: $ref: "#/components/schemas/Snowflake" cursor_5: name: cursor in: query description: Opaque message ID from nextCursor; fetch messages before it. schema: $ref: "#/components/schemas/Snowflake"