1705 lines
56 KiB
YAML
1705 lines
56 KiB
YAML
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
|
||
<KEYCLOAK_ISSUER_URL>/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"
|