HTTP and WebSocket API Reference
Complete reference for the embedded Javalin HTTP/WebSocket server provided by the api module.
Overview
The api module starts an embedded Javalin 6 HTTP server that serves as a bridge between the Kreiscraft web frontend and the Paper server. It exposes:
- JSON REST endpoints for server status, player data, session management, browser↔player linking, and whitelist administration.
- A WebSocket endpoint (
/chat) that relays in-game chat to browser clients and accepts browser chat messages.
The server starts inside ApiModule.enable() and binds to the address and port configured in modules.api. There is no reverse proxy layer in the default setup — the Javalin instance listens directly on the configured TCP port.
Technology: Javalin 6, embedded Jetty, Jackson for JSON serialisation (ObjectMapper configured with FAIL_ON_UNKNOWN_PROPERTIES = false).
Base URL pattern: http://<host>:<port> — e.g. http://0.0.0.0:8080.
Configuration
See Configuration for all modules.api keys, defaults, and reload behaviour.
Authentication and Account Linking
Browser session identity
The API uses an HTTP cookie to identify a browser session. The cookie name is configured via session-cookie.name (default: website_user_uuid). Its value is a random UUID assigned by GET /initialize.
All endpoints that act on behalf of a specific user (link, unlink, poll) read this cookie and parse it as a UUID. If the cookie is absent or malformed, the endpoint returns 401 {"error": "missing_session"}.
Player linking flow
Linking ties a browser session UUID to an in-game player UUID so the frontend can display player-specific data.
- The browser calls
GET /initializeto obtain or reuse a session UUID (stored in the cookie). - The browser calls
POST /link-playerwith a JSON body containing the target player’s username. - The server dispatches to the Bukkit main thread, looks up the player by exact name, and — if the player is online — opens a 3×9 chest GUI for them via
LinkGuiPrompt.- The GUI title and item labels come from the MessageService (
api.link-prompt-title, etc.). - Slot 4 (row 0, centre): paper item showing the browser session identifier (
browser <first-8-chars-of-uuid>). - Row 1, column 2 (slot 11): green wool — Accept.
- Row 1, column 6 (slot 15): red wool — Decline.
- The GUI title and item labels come from the MessageService (
- The browser polls
GET /linked-playeruntil the status changes frompending. - If the player clicks Accept,
VerificationService.confirmVerification()maps the browser UUID → player UUID. Status becomesaccepted. - If the player clicks Decline, or the verification times out (configurable
verification-timeout, default2m), the status becomesdeclined. Expired requests are scrubbed by a background task running everytimeout / 4. - To remove an accepted link the browser calls
POST /unlink-player.
Admin endpoints
GET /whitelist and POST /whitelist require the header:
X-Kreiscraft-Api-Key: <value matching modules.api.admin-api-key>If admin-api-key is blank, both endpoints return 503 {"error": "admin_api_disabled"}.
HTTP Endpoints
GET /status
Description: Returns a current snapshot of server metrics. Updated on the main thread at the configured metrics-refresh-interval.
Auth required: No.
Response:
200 OK— JSON object:
{ "status": "Online", "tps": 20.0, "onlinePlayerCount": 5, "memoryUsedMB": 1024, "memoryMaxMB": 4096, "cpuUsage": 12.5}| Field | Type | Description |
|---|---|---|
status |
String | Always "Online" (placeholder value from ServerMetricsCollector). |
tps |
double | 1-minute TPS average from Server.getTPS()[0]. |
onlinePlayerCount |
int | Number of currently online players. |
memoryUsedMB |
long | Heap memory used (MiB). |
memoryMaxMB |
long | Heap memory max (MiB). |
cpuUsage |
double | Process CPU usage 0–100. Returns -1.0 on JVMs where com.sun.management.OperatingSystemMXBean is unavailable. |
GET /players
Description: Returns the current list of online players with per-player statistics. Updated on the same refresh cycle as /status.
Auth required: No.
Response:
200 OK— JSON array of player objects:
[ { "name": "PlayerName", "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "health": 20, "deaths": 3, "playerKills": 1, "playtimeSeconds": 86400 }]| Field | Type | Description |
|---|---|---|
name |
String | Player’s display name. |
uuid |
String | Player UUID as a dashed string. |
health |
int | Current health, rounded to nearest integer. |
deaths |
int | Lifetime death count (Statistic.DEATHS). |
playerKills |
int | Lifetime player kill count (Statistic.PLAYER_KILLS). |
playtimeSeconds |
long | Total playtime in seconds (Statistic.PLAY_ONE_MINUTE / 20). |
GET /initialize
Description: Initialises or resumes a browser session. Checks for an existing valid UUID cookie; if absent or malformed, generates a new UUID and sets the cookie.
Auth required: No.
Cookie set:
| Attribute | Value |
|---|---|
| Name | modules.api.session-cookie.name (default website_user_uuid) |
| Value | Random UUID v4 string |
| Path | / |
| HttpOnly | true |
| Secure | false (set to true if serving over HTTPS) |
| SameSite | Strict |
| Max-Age | modules.api.session-cookie.max-age in seconds (default 30 days) |
Response:
200 OK:
{ "status": "new", "userId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }or
{ "status": "existing", "userId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }status is "new" when a new cookie was issued, "existing" when the request already carried a valid UUID cookie.
POST /link-player
Description: Initiates a browser→player link request. Looks up the named player on the Bukkit main thread and opens the in-game confirmation GUI.
Auth required: Session cookie (browser UUID).
Request body: application/json
{ "username": "ExactPlayerName" }Response:
| Status | Body | Condition |
|---|---|---|
202 Accepted |
{"status": "verification_sent"} |
GUI opened on the target player. |
401 Unauthorized |
{"error": "missing_session"} |
Cookie absent or not a valid UUID. |
400 Bad Request |
{"error": "invalid_body"} |
Body is not valid JSON or LinkRequest cannot be parsed. |
400 Bad Request |
{"error": "missing_username"} |
username field is null or blank. |
409 Conflict |
{"status": "already_pending"} |
A verification request for this browser session is already pending. |
422 Unprocessable Entity |
{"error": "player_offline"} |
No online player found with that exact name. |
500 Internal Server Error |
{"error": "unexpected_result"} |
Unexpected enum value from VerificationService. |
GET /linked-player
Description: Polls the current link status for the browser session identified by the session cookie.
Auth required: Session cookie (browser UUID).
Response:
200 OK— always returns200. Body varies by status:
{ "status": "not_linked" }{ "status": "pending" }{ "status": "declined" }{ "status": "accepted", "playerData": { "name": "PlayerName", "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "health": 20, "deaths": 3, "playerKills": 1, "playtimeSeconds": 86400 }}When status is "accepted" but the linked player is currently offline, playerData is omitted (null is excluded by @JsonInclude(NON_NULL)).
401 Unauthorized—{"error": "missing_session"}— Cookie absent or invalid.
POST /unlink-player
Description: Removes an accepted browser→player link. Notifies the player in-game if they are online.
Auth required: Session cookie (browser UUID).
Request body: None.
Response:
| Status | Body | Condition |
|---|---|---|
200 OK |
{"status": "unlinked"} |
Link removed. |
400 Bad Request |
{"error": "not_linked"} |
The session had no accepted link. |
401 Unauthorized |
{"error": "missing_session"} |
Cookie absent or invalid. |
After unlinking, the module sends the message key api.unlinked to the player on the main thread (fire-and-forget; no response is delayed).
GET /whitelist
Description: Returns the server whitelist as a sorted list of player names.
Auth required: X-Kreiscraft-Api-Key header matching modules.api.admin-api-key. Disabled when admin-api-key is blank.
Response:
| Status | Body | Condition |
|---|---|---|
200 OK |
["Alice", "Bob", ...] |
Success — sorted, null entries filtered. |
401 Unauthorized |
(empty body) | Missing or wrong API key. |
503 Service Unavailable |
{"error": "admin_api_disabled"} |
admin-api-key is blank. |
POST /whitelist
Description: Adds a Java player to the server whitelist.
Auth required: X-Kreiscraft-Api-Key header.
Request body: application/json
{ "playerName": "ExactPlayerName" }playerName must match ^[a-zA-Z0-9_]{1,16}$ (standard Minecraft Java username rules).
Response:
| Status | Body | Condition |
|---|---|---|
202 Accepted |
{"status": "whitelisted"} |
Player added to whitelist. |
400 Bad Request |
{"error": "invalid_body"} |
Body not valid JSON. |
400 Bad Request |
{"error": "invalid_name"} |
playerName is null, blank, or fails the regex. |
401 Unauthorized |
(empty body) | Missing or wrong API key. |
503 Service Unavailable |
{"error": "admin_api_disabled"} |
admin-api-key is blank. |
The whitelist mutation is dispatched to the Bukkit main thread via Scheduler.runOnMain.
WebSocket Endpoint: GET /chat
Description: Bidirectional WebSocket bridge between the web frontend and in-game chat.
Connection
Connect with a standard WebSocket upgrade to ws://<host>:<port>/chat. No authentication is required to connect. CORS rules (configured via cors-origins) apply to WebSocket upgrade requests in the same way as HTTP requests.
On successful connection the server immediately sends a handshake message:
{ "type": "VERIFICATION", "content": "Connection successful." }Message Format
All messages in both directions are JSON objects:
{ "type": "<TYPE>", "content": "<string>" }| Field | Type | Description |
|---|---|---|
type |
String (enum) | One of CHAT, PING, PONG, VERIFICATION, EXCEPTION. |
content |
String | Message content. Meaning depends on type. |
Message Types
| Type | Direction | Description |
|---|---|---|
CHAT |
Server → Client | An in-game chat message forwarded to the browser. Content uses the legacy §-prefixed colour scheme (e.g. §#ff5500PlayerName§§: hello). |
CHAT |
Client → Server | A chat message sent from the browser. Content is broadcast to in-game chat via MessageService.broadcast("api.chat-format-from-web", "content", <content>) and echoed to all other connected WebSocket clients. |
PING |
Server → Client | Heartbeat ping sent every heartbeat-interval. Content: "heartbeat ping". |
PONG |
Client → Server | Expected reply to PING. Server ignores this message. |
PONG |
Server → Client | Reply to a PING sent by the client. Content: "heartbeat pong". |
VERIFICATION |
Server → Client | Handshake on connection, or future auth messages. Content: "Connection successful.". Clients must not send this type. |
EXCEPTION |
Server → Client | Sent when the server cannot parse an incoming message. Content: "bad message". Clients must not send this type. |
Colour Encoding
In-game chat events received from ChatBridge are rendered using a legacy §-prefixed encoding:
- Each
TextComponentsegment is prefixed with§#rrggbb(hex colour, e.g.§#ff5500). - Segments with no colour use
§§as a double-escape. - Non-
TextComponentchildren (e.g.TranslatableComponent) fall back to the pre-computed plain text string.
This encoding is retained for zero-downtime frontend compatibility. Migration to MiniMessage may happen in the future.
Heartbeat
The server sends a PING to all connected clients on a fixed schedule (heartbeat-interval, default 20 s). Before each ping cycle, sessions with a closed underlying Jetty session are pruned from the connected set. There is no strict client-side pong requirement — the server does not disconnect clients that do not reply to pings.
Threading
All Bukkit API calls triggered by incoming WebSocket messages (e.g. MessageService.broadcast) are dispatched to the Bukkit main thread via Scheduler.runOnMain. WebSocket event handlers run on a Jetty worker thread and must not call Bukkit API directly.
Security
admin-api-keyis required for/whitelist; an empty value disables those endpoints.cors-originsis the only origin-based protection for other endpoints. Do not allow wildcard origins.- The server provides no TLS and no authentication beyond player linking and the admin key. Put a TLS reverse proxy in front of any public deployment and add authentication there if required.
- Player linking authenticates a browser as a player, not as an administrator.
Error Response Format
There is no single standardised error envelope across all endpoints. Errors are returned as plain JSON objects with an "error" key, or as an empty body. The content-type is application/json for all JSON error bodies.
Common error bodies:
| Body | Meaning |
|---|---|
{"error": "missing_session"} |
Session cookie absent or not a valid UUID. |
{"error": "invalid_body"} |
Request body could not be parsed as the expected record. |
{"error": "admin_api_disabled"} |
admin-api-key is blank in config. |
{"error": "not_linked"} |
No accepted link exists for this session. |
{"error": "player_offline"} |
Target player is not online. |