# HTTP Web API **Package:** `me.bobert.netneutrality.http` (outbound client) and `me.bobert.netneutrality.http.server` (server). **Server:** embedded [Javalin](https://javalin.io) 7 with the OpenAPI and Swagger UI plugins. The bot exposes an internal HTTP API for health monitoring, control, guild introspection, and music control — intended for an external dashboard/companion, not for end users in Discord. ## Server startup `WebApi` is a [`Lifecycle`](utilities-and-bootstrap.md#applicationcontext--lifecycle) component (priority 70) registered in [`App.buildContext()`](architecture.md#startup-lifecycle). It receives `SettingsProvider`, `GuildServiceProvider`, `MusicService`, and suppliers for the live `JDA` / shutdown callback via its constructor. On `start()` it: - reads the port from `NETNEUTRALITY_API_PORT` (default `9000`), - creates a `Javalin` app (Javalin 7 requires routes and exception/error handlers to be registered upfront inside `Javalin.create`), - registers the `OpenApiPlugin` (title "Net Neutrality API", `X-API-Key` security scheme) and the `SwaggerPlugin`, - installs the `ApiKeyFilter` as a `before` handler, - registers the route groups (below), the exception handlers and the 404 handler, and - starts the server on the port. On `stop()` the Javalin app is stopped. ## Authentication `ApiKeyFilter` compares the `X-API-Key` request header with the `NETNEUTRALITY_API_KEY` setting. A missing/invalid key — or no configured key at all — yields `401` ("Missing or invalid API key"). Public paths that skip the check: `/`, and anything starting with `/openapi`, `/swagger` or `/webjars` (Swagger UI and its assets). ## Error handling - `HttpResponseException` (e.g. `BadRequestResponse`, `NotFoundResponse`) → `{"status":"error","message":"..."}` with the exception's status. - Any other exception → logged, `500` with `{"status":"error"}`. - Unknown paths → `404` with `{"status":"error","message":"Not found"}`. ## Route groups Each group is a `*Routes` class with a static `register(RoutesConfig, ...)` method; handlers are annotated with `@OpenApi` so they appear in the generated OpenAPI document. Request/response shapes live in the `http.server.dto` package. | Class | Endpoints | |---|---| | `WebApi` | `GET /` — HTML liveness message. | | `GuildRoutes` | `GET /guilds` (optional `user_id` query restricts to mutual guilds); `GET /guilds/owner`; `GET /guilds/{guildId}/owner`; `/channels`; `/roles`; `/members/{userId}`, `/members/{userId}/access`, `/members/{userId}/roles`, `/members/{userId}/admin`; `/voice-channels`, `/voice-channels/{channelId}`; `GET /guilds/{guildId}/config`; `PUT /guilds/{guildId}/debug`, `/logging`, `/log-channel`. | | `ActionRoutes` | `GET /actions/status` — JDA health + gateway ping; `POST /actions/shutdown` — graceful shutdown. | | `SettingsRoutes` | `GET /settings/catalog` — every known setting with default and description; `GET /settings/current/{guildId}` — current guild settings; `PUT /settings/current/{guildId}/{name}` — update a setting; `PUT /settings/reload/{guildId}` — invalidate & reload the settings cache. | | `StatsRoutes` | `GET /stats/bot`; `GET /stats/guilds/{guildId}`. | | `MusicRoutes` | See below. | ### Music endpoints Mirror the slash commands (see [music.md](music.md)); announcements are also posted to the guild's music channel when a context is attached. | Method & path | Purpose | |---|---| | `GET /music/sources` | Supported music sources. | | `GET /music/{guildId}/status` | Connection, playback, current track, position, queue size. | | `GET /music/{guildId}/queue` | Queued tracks. | | `POST /music/{guildId}/search` | Resolve a query (optional `searchEngine`) into candidates; returns a `search_id`. Nothing is queued. | | `POST /music/{guildId}/queue` | Queue a candidate by `searchId` + `index` (or all with `queueAll=true`) for `userId`; the bot must already be in a voice channel. | | `DELETE /music/{guildId}/queue` | Clear the queue, or remove one track with `?position=` (1-based). | | `PATCH /music/{guildId}/queue/reorder` | Move a track (`fromPosition` → `toPosition`). | | `POST /music/{guildId}/skip`, `/stop`, `/pause`, `/resume`, `/shuffle`, `/leave` | Playback controls. | | `GET` / `PUT /music/{guildId}/volume` | Read / set volume (0–100). | | `GET` / `PUT /music/{guildId}/autoplay` | Read / set autoplay (`{"enabled": true}`). In-memory, resets when the player is destroyed. | | `PUT /music/{guildId}/seek` | Seek to `positionMs`. | | `GET /music/{guildId}/vote-skip` | Vote-skip status. | | `POST /music/{guildId}/vote-skip/{userId}` | Cast a vote on behalf of a member. | | `GET /music/{guildId}/dj/{userId}` | Whether the member is currently allowed to act as DJ. | Search results are held in `PendingSearchResults` and expire after a TTL (returned as `search_id_ttl_seconds`). ## HTTP client — `WebModule` `WebModule` (package `me.bobert.netneutrality.http`) wraps an `OkHttpClient` for the bot's own outbound HTTP calls (e.g. checking for updates via GitLab). Responses are cached in `ResponseCache`, a `LinkedHashMap`-based LRU limited to 50 entries. ## Notes - All routes except the public ones require the API key. Even so, the API is expected to be bound to a trusted/internal network (e.g. within the Docker network); `/actions/shutdown` is destructive, so the port should not be exposed publicly.