Admin API & Web Interface
The admin API plugin exposes a REST API and an embedded web dashboard for managing your Infrarust proxy over HTTP. You can list players, kick or move them between servers, manage bans, check server health, reload configuration, and stream live events and logs, all without touching the Minecraft client or the terminal.
The plugin is always compiled into the binary. It activates when a [web] section is present in infrarust.toml.
Enabling the plugin
Add a [web] section to your infrarust.toml:
[web]That is enough. All fields have defaults:
| Option | Type | Default | Description |
|---|---|---|---|
enable_api | bool | true | Start the REST API |
enable_webui | bool | true | Serve the embedded web dashboard |
bind | string | "127.0.0.1:8080" | Socket address the HTTP server listens on |
api_key | string | (see below) | Bearer token for authentication. Must be at least 16 characters. |
cors_origins | string[] | [] | Allowed CORS origins. Empty means no CORS headers are sent. |
rate_limit.requests_per_minute | u64 | 60 | Maximum requests per minute across all clients on authenticated endpoints |
To change the bind address or disable the web UI while keeping the API:
[web]
bind = "127.0.0.1:9090"
enable_webui = false2
3
API key behavior
If api_key is not set and bind resolves to a loopback address (127.0.0.1, ::1, localhost), the plugin generates a random UUID v4 key at startup and logs it as a warning:
WARN No API key configured for loopback bind (127.0.0.1:8080) — generated an ephemeral key: a1b2c3d4-e5f6-7890-abcd-ef1234567890This key is not written to disk. It changes on every restart. For a persistent key, set one explicitly:
[web]
api_key = "your-strong-key-here"2
WARNING
If bind is set to a non-loopback address (e.g. 0.0.0.0:8080) and no api_key is configured, the plugin refuses to start. You must supply a key of at least 16 characters before binding to any externally reachable address.
DANGER
Do not expose the admin API to the public internet without a reverse proxy or firewall. The default bind 127.0.0.1 restricts access to the local machine.
Authentication
All endpoints except GET /api/v1/health require a Bearer token in the Authorization header:
curl -H "Authorization: Bearer YOUR_API_KEY" http://127.0.0.1:8080/api/v1/proxyThe token is compared using constant-time verification to prevent timing attacks.
SSE authentication
Server-Sent Events endpoints (/api/v1/events, /api/v1/logs) cannot use the Authorization header because the browser EventSource API does not support custom headers. These endpoints authenticate via a token query parameter instead:
GET /api/v1/events?token=YOUR_API_KEY&types=player.join,player.leaveRate limiting
Authenticated endpoints are rate-limited to requests_per_minute (default 60). The counter is shared across all clients. It tracks total requests to the API, not per-IP. The health endpoint is exempt.
Response headers on every authenticated request:
| Header | Description |
|---|---|
X-RateLimit-Limit | Allowed requests per minute |
X-RateLimit-Remaining | Remaining requests in the current window |
X-RateLimit-Reset | Seconds until the window resets |
When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header.
API reference
All responses follow a consistent format:
{
"data": { ... }
}2
3
{
"data": [ ... ],
"meta": {
"total": 42,
"page": 1,
"per_page": 20,
"total_pages": 3
}
}2
3
4
5
6
7
8
9
{
"error": {
"code": "NOT_FOUND",
"message": "Player 'Steve' not found"
}
}2
3
4
5
6
Paginated endpoints accept ?page=1&per_page=20 query parameters. Maximum per_page is 100.
Public endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/health | Health check. Returns {"status": "ok"}. No auth required. |
Proxy
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/proxy | Proxy status: version, uptime, player count, server count, features, memory usage |
| POST | /api/v1/proxy/shutdown | Graceful proxy shutdown |
| POST | /api/v1/proxy/gc | Trigger garbage collection (no-op in Rust, returns success) |
Players
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/players | List online players (paginated, filterable by server and mode) |
| GET | /api/v1/players/count | Player count grouped by server and proxy mode |
| GET | /api/v1/players/{id_or_username} | Get a specific player's details |
| POST | /api/v1/players/broadcast | Broadcast a message to all online players |
| POST | /api/v1/players/{username}/kick | Kick a player |
| POST | /api/v1/players/{username}/send | Transfer a player to another server |
| POST | /api/v1/players/{username}/message | Send a chat message to a player |
Servers
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/servers | List all configured servers |
| GET | /api/v1/servers/{id} | Get a server's configuration and state |
| POST | /api/v1/servers | Create a server (API-managed, stored in the plugin's data directory) |
| PUT | /api/v1/servers/{id} | Update a server's configuration |
| DELETE | /api/v1/servers/{id} | Delete an API-managed server |
| POST | /api/v1/servers/{id}/start | Start a server |
| POST | /api/v1/servers/{id}/stop | Stop a server |
| GET | /api/v1/servers/{id}/health | Real-time health check (pings the Minecraft server, 5s timeout) |
| GET | /api/v1/servers/{id}/health/cached | Last cached health check result |
Bans
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/bans | List all bans (paginated) |
| GET | /api/v1/bans/check/{target_type}/{value} | Check if a username, UUID, or IP is banned |
| POST | /api/v1/bans | Create a ban. Target types: username, uuid, ip |
| DELETE | /api/v1/bans/{target_type}/{value} | Remove a ban |
Plugins
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/plugins | List all loaded plugins |
| GET | /api/v1/plugins/{id} | Get a specific plugin's info |
| POST | /api/v1/plugins/{id}/enable | Enable a plugin |
| POST | /api/v1/plugins/{id}/disable | Disable a plugin |
Configuration
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/config/providers | List active configuration providers |
| POST | /api/v1/config/reload | Reload proxy configuration from disk |
Statistics
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/stats | Overview: players online, servers total, uptime, breakdowns by server and state |
| GET | /api/v1/events/recent | Last 100 activity events (excludes stats ticks) |
Logs
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/logs/history | Recent log entries from the ring buffer. Query: ?n=100&level=warn&target=infrarust_core |
Server-Sent Events (SSE)
Two streaming endpoints provide real-time data without polling. Both use query-parameter authentication.
Event stream
GET /api/v1/events?token=YOUR_API_KEY&types=player.join,player.leaveAvailable event types:
| Event type | Fired when |
|---|---|
player.join | A player connects |
player.leave | A player disconnects |
player.switch | A player moves between servers |
server.state_change | A server's state changes |
config.reload | Configuration is reloaded |
ban.created | A ban is created |
ban.removed | A ban is removed |
stats.tick | Periodic stats snapshot (every 5 seconds) |
Omit the types parameter to receive all events. The stream sends a keep-alive comment every 15 seconds.
Log stream
GET /api/v1/logs?token=YOUR_API_KEY&level=warn&target=infrarust_coreStreams log entries in real time. Filter by minimum level (trace, debug, info, warn, error) and target module prefix.
Web dashboard
When enable_webui is true, the plugin serves an embedded web frontend at the root URL (http://127.0.0.1:8080/). The frontend is a Nuxt SPA bundled into the binary at compile time.
Non-API routes serve static files from the embedded bundle. If a requested file doesn't exist, the server returns index.html for client-side routing. API routes (/api/*) that don't match a defined endpoint return 404.
Cache headers:
| Path pattern | Cache-Control |
|---|---|
_nuxt/* | public, max-age=31536000, immutable |
index.html, 200.html | no-cache |
| Other static files | public, max-age=3600 |
Example: list online players
curl -s \
-H "Authorization: Bearer YOUR_API_KEY" \
http://127.0.0.1:8080/api/v1/players | jq2
3
{
"data": [
{
"id": 1,
"username": "Steve",
"uuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5",
"ip": "203.0.113.7",
"server": "survival",
"is_active": true,
"connected_since": "2025-01-15T10:30:00Z",
"connected_duration": "1h 12m 5s"
}
],
"meta": {
"total": 1,
"page": 1,
"per_page": 20,
"total_pages": 1
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Example: kick a player
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Server maintenance"}' \
http://127.0.0.1:8080/api/v1/players/Steve/kick2
3
4
5
Example: subscribe to events
const events = new EventSource(
'http://127.0.0.1:8080/api/v1/events?token=YOUR_API_KEY&types=player.join,player.leave'
);
events.addEventListener('player.join', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} joined ${data.server}`);
});
events.addEventListener('player.leave', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} left`);
});2
3
4
5
6
7
8
9
10
11
12
13