# Languard Server Manager — API Reference ## Base URL ``` http://localhost:8000/api ``` All HTTP endpoints are prefixed with `/api`. The WebSocket endpoint is at `/ws` (no `/api` prefix). ## Authentication All endpoints except `POST /auth/login` and `GET /system/health` require a JWT bearer token: ``` Authorization: Bearer ``` For WebSocket, pass the token as a query parameter: ``` ws://localhost:8000/ws?token=&server_id=1 ``` JWT payload: ```json { "sub": "user_id", "username": "string", "role": "admin|viewer", "exp": 1745000000 } ``` ### Roles | Role | Access | |---------|-----------------------------------------------------------| | `admin` | Full access: create, update, delete, start/stop servers | | `viewer`| Read-only: list servers, view config (sensitive masked) | ## Standard Response Envelope All responses use a consistent envelope: **Success:** ```json { "success": true, "data": { ... }, "error": null } ``` **Error:** ```json { "success": false, "data": null, "error": { "code": "NOT_FOUND", "message": "Server with id 5 not found" } } ``` Some error responses (from FastAPI HTTPException) use a `detail` object instead: ```json { "detail": { "code": "NOT_FOUND", "message": "Server 5 not found" } } ``` ## HTTP Status Codes | Code | Meaning | |------|------------------------------------------------------------| | 200 | Success | | 201 | Created (resource successfully created) | | 204 | No content (successful DELETE) | | 400 | Bad request (validation error, capability not supported) | | 401 | Unauthenticated (missing or invalid token) | | 403 | Forbidden (insufficient role — admin required) | | 404 | Not found (resource or capability not available) | | 409 | Conflict (already running, port in use, version conflict) | | 413 | Payload too large (file upload exceeds 500 MB) | | 422 | Unprocessable entity (Pydantic validation failure) | | 500 | Internal server error | | 503 | Service unavailable (RCon connection failed) | ## Capability-Based Routing Some endpoints depend on the server's game adapter supporting a specific capability. If the adapter does not support the capability, the endpoint returns **400** with: ```json { "success": false, "data": null, "error": { "code": "NOT_SUPPORTED", "message": "Game type 'rust' does not support mission management" } } ``` --- ## Auth Endpoints ### POST /auth/login Authenticate and receive a JWT token. Public endpoint (no auth required). **Request:** ```json { "username": "admin", "password": "secret" } ``` **Response 200:** ```json { "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer", "expires_in": 86400, "user": { "id": 1, "username": "admin", "role": "admin" } }, "error": null } ``` **Error 401:** Invalid credentials. ```json { "success": false, "data": null, "error": { "code": "UNAUTHORIZED", "message": "Invalid credentials" } } ``` --- ### POST /auth/logout Invalidate session (client-side token deletion; no server-side blacklist). **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "message": "Logged out" }, "error": null } ``` --- ### GET /auth/me Return the currently authenticated user. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "id": 1, "username": "admin", "role": "admin" }, "error": null } ``` --- ### PUT /auth/password Change the authenticated user's own password. Any authenticated user can change their own password (not admin-only). **Auth:** Required (any role) **Request:** ```json { "current_password": "old_password", "new_password": "new_password" } ``` **Response 200:** ```json { "success": true, "data": { "message": "Password changed" }, "error": null } ``` **Error 401:** Current password is incorrect. --- ### GET /auth/users List all users. **Auth:** Admin required **Response 200:** ```json { "success": true, "data": [ { "id": 1, "username": "admin", "role": "admin", "created_at": "2026-04-16T00:00:00", "last_login": "2026-04-17T10:30:00" } ], "error": null } ``` --- ### POST /auth/users Create a new user. **Auth:** Admin required **Request:** ```json { "username": "viewer1", "password": "secure_password", "role": "viewer" } ``` `role` defaults to `"viewer"` if omitted. Valid values: `"admin"`, `"viewer"`. **Response 201:** ```json { "success": true, "data": { "id": 2, "username": "viewer1", "role": "viewer", "created_at": "2026-04-17T12:00:00" }, "error": null } ``` **Error 409:** Username already taken. --- ### DELETE /auth/users/{user_id} Delete a user. Cannot delete yourself. **Auth:** Admin required **Response 204:** No content. **Error 400:** Attempting to delete your own account. --- ## Games Endpoints ### GET /games List all registered game types with their capabilities. **Auth:** Not required (public) **Response 200:** ```json { "success": true, "data": [ { "game_type": "arma3", "display_name": "Arma 3", "version": "1.0.0", "capabilities": [ "config_generator", "process_config", "log_parser", "remote_admin", "mission_manager", "mod_manager", "ban_manager" ] } ], "error": null } ``` --- ### GET /games/{game_type} Get details for a specific game type, including capabilities, config sections, and allowed executables. **Auth:** Not required (public) **Path params:** | Parameter | Type | Description | |-------------|--------|------------------------| | `game_type` | string | Game type identifier | **Response 200:** ```json { "success": true, "data": { "game_type": "arma3", "display_name": "Arma 3", "version": "1.0.0", "schema_version": "1.0", "capabilities": [ "config_generator", "process_config", "log_parser", "remote_admin", "mission_manager", "mod_manager", "ban_manager" ], "config_sections": ["server", "basic", "profile", "launch", "rcon"], "allowed_executables": ["arma3server_x64.exe", "arma3server.exe"] }, "error": null } ``` **Error 404:** Unknown game type. --- ### GET /games/{game_type}/config-schema Returns JSON Schema for each config section defined by the adapter. Used by the frontend to build dynamic config forms. **Auth:** Not required (public) **Path params:** | Parameter | Type | Description | |-------------|--------|------------------------| | `game_type` | string | Game type identifier | **Response 200:** ```json { "success": true, "data": { "server": { "...": "JSON Schema for server.cfg params" }, "basic": { "...": "JSON Schema for basic.cfg params" }, "profile": { "...": "JSON Schema for Arma3Profile params" }, "launch": { "...": "JSON Schema for launch params" }, "rcon": { "...": "JSON Schema for RCon params" } }, "error": null } ``` --- ### GET /games/{game_type}/defaults Default config values for new server creation. **Auth:** Not required (public) **Path params:** | Parameter | Type | Description | |-------------|--------|------------------------| | `game_type` | string | Game type identifier | **Response 200:** ```json { "success": true, "data": { "server": { "hostname": "My Arma 3 Server", "max_players": 40 }, "basic": { "min_bandwidth": 800000 }, "profile": { "reduced_damage": 0 }, "launch": { "world": "empty", "limit_fps": 50 }, "rcon": { "max_ping": 200, "enabled": 1 } }, "error": null } ``` --- ## System Endpoints ### GET /system/health Public health check. Used by load balancers and Docker health probes. **Auth:** Not required **Response 200:** ```json { "status": "ok" } ``` Note: This endpoint does not use the standard envelope. --- ### GET /system/status System status including version and running server counts. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "version": "1.0.0", "running_servers": 2, "total_servers": 3, "supported_games": ["arma3"] } } ``` --- ## Server Endpoints ### GET /servers List all servers with current status and live metrics. **Auth:** Required (any role) **Query params:** | Parameter | Type | Description | |-------------|--------|------------------------------| | `game_type` | string | Filter servers by game type | **Response 200:** ```json { "success": true, "data": [ { "id": 1, "name": "Main Server", "description": "Primary COOP server", "game_type": "arma3", "status": "running", "pid": 12345, "game_port": 2302, "rcon_port": 2306, "auto_restart": true, "max_restarts": 3, "restart_count": 0, "player_count": 15, "cpu_percent": 34.2, "ram_mb": 1850.5, "started_at": "2026-04-16T10:00:00Z" } ], "error": null } ``` `cpu_percent`, `ram_mb`, and `player_count` are merged from live metrics. They will be `null` if no metrics are available. --- ### POST /servers Create a new server. The `game_type` determines which adapter handles this server. **Auth:** Admin required **Request:** ```json { "name": "Main Server", "description": "Primary COOP server", "game_type": "arma3", "exe_path": "C:/Arma3Server/arma3server_x64.exe", "game_port": 2302, "rcon_port": 2306, "auto_restart": true, "max_restarts": 3 } ``` | Field | Type | Required | Default | Validation | |----------------|---------|----------|----------|---------------------------| | `name` | string | Yes | — | | | `description` | string | No | `null` | | | `game_type` | string | No | `"arma3"`| Must be a registered type | | `exe_path` | string | Yes | — | Filename must be in allowlist | | `game_port` | integer | Yes | — | 1024–65535, must not be in use | | `rcon_port` | integer | No | auto | 1024–65535, auto = game_port + 3 | | `auto_restart` | boolean | No | `false` | | | `max_restarts` | integer | No | `3` | 0–20 | The adapter provides default config values for all sections. Auto-generated credentials (e.g., `password_admin`, `rcon_password` for Arma 3) are returned in the creation response only. **Response 201:** Returns the full server object. **Errors:** - **400** `GAME_TYPE_NOT_FOUND` — Unknown game type - **400** `EXE_NOT_ALLOWED` — Executable filename not in adapter allowlist - **409** `PORT_IN_USE` — Game port or RCon port already in use --- ### GET /servers/{server_id} Get server detail with full status. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "id": 1, "name": "Main Server", "description": "Primary COOP server", "game_type": "arma3", "status": "running", "pid": 12345, "exe_path": "C:/Arma3Server/arma3server_x64.exe", "game_port": 2302, "rcon_port": 2306, "auto_restart": true, "max_restarts": 3, "restart_count": 0, "player_count": 15, "cpu_percent": 34.2, "ram_mb": 1850.5, "started_at": "2026-04-16T10:00:00Z", "uptime_seconds": 3600 }, "error": null } ``` **Error 404:** Server not found. --- ### PUT /servers/{server_id} Update server metadata (name, description, exe_path, ports). Only provided fields are updated; omitted fields keep their current values. **Auth:** Admin required **Request:** ```json { "name": "Updated Server Name", "description": "New description", "exe_path": "C:/Arma3Server/arma3server_x64.exe", "game_port": 2302, "rcon_port": 2306, "auto_restart": false, "max_restarts": 5 } ``` All fields are optional. Port validation (1024–65535) applies if provided. **Response 200:** Returns the updated server object. --- ### DELETE /servers/{server_id} Delete a server. The server must be in a stopped, crashed, or error state. **Auth:** Admin required **Response 204:** No content. **Error 409:** `SERVER_NOT_STOPPED` — Server is currently running. Note: Also deletes the server's directory on disk (`servers/{id}/`). --- ### POST /servers/{server_id}/start Start the server. The system resolves the adapter, writes config files atomically, builds launch arguments, starts the process, and attaches monitoring threads. **Auth:** Admin required **Response 200:** ```json { "success": true, "data": { "status": "starting", "pid": 12345 }, "error": null } ``` **Errors:** - **400** `EXE_NOT_ALLOWED` — Executable not in adapter allowlist - **400** `INVALID_CONFIG` — Config validation or launch args failed - **409** `SERVER_ALREADY_RUNNING` — Server is already running or starting - **409** `PORT_IN_USE` — Game port or RCon port already in use - **500** `CONFIG_WRITE_ERROR` — Failed to write config files to disk --- ### POST /servers/{server_id}/stop Graceful stop. Sends a shutdown signal via the adapter's remote admin, then force-kills after 30 seconds if the process has not exited. **Auth:** Admin required **Request (optional body):** ```json { "force": false, "reason": "Scheduled maintenance" } ``` | Field | Type | Required | Default | Description | |----------|---------|----------|---------|------------------------------------------| | `force` | boolean | No | `false` | Skip graceful shutdown, terminate immediately | | `reason` | string | No | `null` | Optional reason for the stop | **Response 200:** ```json { "success": true, "data": { "status": "stopped" }, "error": null } ``` **Error 409:** `SERVER_NOT_RUNNING` — Server is already stopped or crashed. --- ### POST /servers/{server_id}/restart Stop then start the server. Equivalent to calling stop then start sequentially. **Auth:** Admin required **Response 200:** Returns the start result (same shape as start). --- ### POST /servers/{server_id}/kill Force-kill the server process immediately. Use for emergency situations only; does not attempt graceful shutdown. **Auth:** Admin required **Response 200:** ```json { "success": true, "data": { "status": "stopped" }, "error": null } ``` --- ## Server Config Endpoints Config sections are defined by the server's game adapter. The core `game_configs` table stores them as JSON. Each section tracks a `config_version` for optimistic locking. ### GET /servers/{server_id}/config Get all config sections combined. Sensitive fields (passwords) are masked with `"***"`. **Auth:** Required (any role — sensitive fields are masked for non-admin viewers) **Response 200:** ```json { "success": true, "data": { "server": { "hostname": "My Server", "max_players": 40, "_meta": { "config_version": 3, "schema_version": "1.0" } }, "basic": { "max_bandwidth": 50000000, "_meta": { "config_version": 1, "schema_version": "1.0" } }, "profile": { "third_person_view": 0, "_meta": { "config_version": 2, "schema_version": "1.0" } }, "launch": { "world": "empty", "limit_fps": 50, "_meta": { "config_version": 1, "schema_version": "1.0" } }, "rcon": { "rcon_password": "***", "max_ping": 200, "enabled": 1, "_meta": { "config_version": 1, "schema_version": "1.0" } } }, "error": null } ``` --- ### GET /servers/{server_id}/config/schema Returns per-field widget hints for the frontend config editor. Used by `ConfigEditor` to render the correct UI widget for each field. Covers all ~80 Arma 3 config fields across 5 sections. **Auth:** Required (any role) **Widget types:** - `text` — Text input - `password` — Password input (masked) - `number` — Numeric input with optional `min`/`max` - `toggle` — Boolean toggle (0/1) - `select` — Dropdown with `options` array. Options may be `["value1", "value2"]` or `["0 - Never", "1 - Always"]` format - `textarea` — Multi-line text area - `tag-list` — Dynamic string list (add/remove items) - `hidden` — Field not displayed in UI (managed elsewhere; e.g., `missions` managed by Missions tab) **Response 200:** ```json { "success": true, "data": { "server": { "hostname": { "widget": "text", "label": "Server Hostname" }, "max_players": { "widget": "number", "label": "Max Players", "min": 1, "max": 1000 }, "password": { "widget": "password", "label": "Player Password" }, "forced_difficulty": { "widget": "select", "label": "Difficulty Preset", "options": ["0 - Recruit", "1 - Regular", "2 - Veteran", "3 - Custom"] }, "battleye": { "widget": "toggle", "label": "BattleEye Anti-Cheat" }, "motd_lines": { "widget": "textarea", "label": "Message of the Day (one line per row)" }, "admin_uids": { "widget": "tag-list", "label": "Admin Steam UIDs", "placeholder": "76561198000000000" }, "missions": { "widget": "hidden", "label": "Missions" } }, "rcon": { "rcon_password": { "widget": "password", "label": "RCon Password" } } }, "error": null } ``` Returns `{}` if the adapter does not implement `get_ui_schema()`. --- ### GET /servers/{server_id}/config/preview Rendered config for preview. Admin only because it may contain plaintext credentials. **Auth:** Admin required Returns a dict of `{label: rendered_content}` where labels are filenames for file-based configs, variable names for env-var configs, or argument names for CLI configs. **Response 200:** ```json { "success": true, "data": { "server.cfg": "hostname = \"My Server\";\nmaxPlayers = 40;\n...", "basic.cfg": "// Generated basic.cfg\n...", "server.Arma3Profile": "// Generated profile\n..." }, "error": null } ``` --- ### GET /servers/{server_id}/config/{section} Get a single config section. Section names are defined by the adapter (e.g., `server`, `basic`, `profile`, `launch`, `rcon` for Arma 3). **Auth:** Required (any role — sensitive fields are masked) **Response 200:** ```json { "success": true, "data": { "hostname": "My Server", "max_players": 40, "_meta": { "config_version": 3, "schema_version": "1.0" } }, "error": null } ``` **Error 404:** Config section not found (not a valid section for this adapter). --- ### PUT /servers/{server_id}/config/{section} Update a config section. Uses **optimistic locking** — the request must include `config_version` from the last read. Validated against the adapter's Pydantic model for that section. Omitted fields keep their current values (partial update). **Auth:** Admin required **Request:** ```json { "hostname": "Updated Server Name", "max_players": 64, "config_version": 3 } ``` `config_version` is required for conflict detection. Any omitted field retains its current value. **Arma 3 section examples:** `server` section: ```json { "hostname": "My Arma Server", "max_players": 40, "battleye": 1, "verify_signatures": 2, "motd_lines": ["Welcome!", "Have fun"], "motd_interval": 5.0, "config_version": 3 } ``` `basic` section: ```json { "max_bandwidth": 50000000, "max_msg_send": 256, "config_version": 1 } ``` `profile` section: ```json { "third_person_view": 0, "weapon_crosshair": 0, "ai_level_preset": 3, "skill_ai": 0.7, "precision_ai": 0.6, "config_version": 2 } ``` `launch` section: ```json { "world": "empty", "limit_fps": 50, "auto_init": false, "load_mission_to_memory": true, "config_version": 1 } ``` `rcon` section: ```json { "rcon_password": "newpassword", "rcon_port": 2306, "max_ping": 300, "enabled": 1, "config_version": 1 } ``` Note: `rcon_port` is stored in the `servers` table, not in the config JSON. The service layer updates both tables as needed. **Response 200:** Returns the updated section (sensitive fields masked). **Error 409 — Optimistic locking conflict:** ```json { "success": false, "data": null, "error": { "code": "CONFIG_VERSION_CONFLICT", "message": "Config was modified by another user. Re-read and merge.", "current_config": { "...": "latest values" }, "current_version": 5 } } ``` **Error 422:** Pydantic validation failure — the submitted values do not match the adapter's schema. --- ## RCon Endpoints ### POST /servers/{server_id}/rcon/command Send an RCon command to a running server. Requires the adapter to support the `remote_admin` capability. **Auth:** Admin required **Request:** ```json { "command": "#restart" } ``` **Response 200:** ```json { "success": true, "data": { "response": "Command executed" }, "error": null } ``` **Errors:** - **400** `NOT_SUPPORTED` — Game type does not support RCon - **400** `NO_RCON_PASSWORD` — RCon password not configured for this server - **503** `RCON_ERROR` — RCon connection or command failed **Arma 3 available RCon commands:** | Command | Description | |---------|-------------| | `#restart` | Restart current mission | | `#reassign` | Restart with roles unassigned | | `#missions` | Open mission selection | | `#lock` | Lock the server | | `#unlock` | Unlock the server | | `#mission NAME.TERRAIN [difficulty]` | Load a specific mission | | `#shutdown` | Shut down the server | | `#monitor N` | Toggle performance monitoring | | `say -1 MESSAGE` | Message all players | --- ## Player Endpoints ### GET /servers/{server_id}/players List currently connected players (cached from RemoteAdminPollerThread). **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "server_id": 1, "player_count": 2, "players": [ { "slot_id": "1", "name": "PlayerOne", "ping": 45 }, { "slot_id": "2", "name": "PlayerTwo", "ping": 78 } ] }, "error": null } ``` --- ### GET /servers/{server_id}/players/history Get historical player session records. **Auth:** Required (any role) **Query params:** | Parameter | Type | Default | Description | |-----------|---------|---------|------------------------------| | `limit` | integer | 100 | Maximum records to return | | `offset` | integer | 0 | Pagination offset | | `search` | string | — | Filter by player name | **Response 200:** ```json { "success": true, "data": { "total": 150, "items": [ { "id": 1, "player_name": "PlayerOne", "guid": "abc123...", "joined_at": "2026-04-16T10:15:00Z", "left_at": "2026-04-16T11:30:00Z" } ] }, "error": null } ``` --- ## Ban Endpoints ### GET /servers/{server_id}/bans List all active bans for a server. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": [ { "id": 1, "server_id": 1, "guid": "abc123...", "name": null, "reason": "Cheating", "banned_by": "admin", "ban_type": "GUID", "duration_minutes": 0, "expires_at": null, "created_at": "2026-04-16T10:00:00Z" } ], "error": null } ``` --- ### POST /servers/{server_id}/bans Create a new ban. Writes to the database and syncs to the game's `bans.txt` file (if adapter supports `ban_manager`). **Auth:** Admin required **Request:** ```json { "player_uid": "abc123...", "ban_type": "GUID", "reason": "Cheating", "duration_minutes": 0 } ``` | Field | Type | Required | Default | Description | |--------------------|---------|----------|-----------|---------------------------------| | `player_uid` | string | Yes | — | GUID or IP to ban | | `ban_type` | string | No | `"GUID"` | Must be `"GUID"` or `"IP"` | | `reason` | string | No | `""` | Reason for the ban | | `duration_minutes` | integer | No | `0` | 0 = permanent, >0 = timed ban | **Response 201:** ```json { "success": true, "data": { "id": 2, "server_id": 1, "guid": "abc123...", "reason": "Cheating", "ban_type": "GUID", "expires_at": null }, "error": null } ``` Note: If the `bans.txt` file sync fails, the database ban is still created. The error is logged but does not fail the request. --- ### DELETE /servers/{server_id}/bans/{ban_id} Revoke (deactivate) a ban. Removes from `bans.txt` if the adapter supports `ban_manager`. **Auth:** Admin required **Response 200:** ```json { "success": true, "data": { "message": "Ban 2 revoked" }, "error": null } ``` **Error 404:** Ban not found or does not belong to this server. --- ## Mission Endpoints All mission endpoints require the adapter to support the `mission_manager` capability. Returns **400** `NOT_SUPPORTED` if the game type does not support missions. ### GET /servers/{server_id}/missions List all available mission/scenario files on disk. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "server_id": 1, "total": 2, "missions": [ { "name": "MyMission.Altis", "filename": "MyMission.Altis.pbo", "size_bytes": 102400, "terrain": "Altis" } ] }, "error": null } ``` --- ### POST /servers/{server_id}/missions Upload a mission file. **Multipart form-data**. Maximum file size: **500 MB**. File extension is validated by the adapter (e.g., `.pbo` for Arma 3). **Auth:** Admin required **Request:** `multipart/form-data` with field `file`. **Response 201:** ```json { "success": true, "data": { "filename": "NewMission.Stratis.pbo", "mission_name": "NewMission.Stratis", "terrain": "Stratis", "file_size": 51200 }, "error": null } ``` **Errors:** - **400** `NO_FILENAME` — No filename provided in upload - **400** `ADAPTER_ERROR` — Upload failed (wrong extension, etc.) - **413** `FILE_TOO_LARGE` — File exceeds 500 MB --- ### GET /servers/{server_id}/missions/rotation Get the current mission rotation from the server config. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "missions": [ { "name": "MyMission.Altis", "difficulty": "Regular" }, { "name": "TvT.Stratis", "difficulty": "Veteran" } ] }, "error": null } ``` --- ### PUT /servers/{server_id}/missions/rotation Replace the mission rotation. Uses **optimistic locking** — must include `config_version` from the last server config read. **Auth:** Admin required **Request:** ```json { "missions": [ { "name": "MyMission.Altis", "difficulty": "Regular" }, { "name": "TvT.Stratis", "difficulty": "" } ], "config_version": 3 } ``` `difficulty` can be `""` for default, or one of `"Recruit"`, `"Regular"`, `"Veteran"`, `"Custom"`. **Response 200:** ```json { "success": true, "data": { "missions": [ ... ] }, "error": null } ``` **Error 409:** Config version conflict — re-fetch and retry. --- ### DELETE /servers/{server_id}/missions/{filename} Delete a mission file by filename. Removes the file from disk. **Auth:** Admin required **Path params:** | Parameter | Type | Description | |------------|--------|---------------------------------| | `filename` | string | Mission filename (e.g. `MyMission.Altis.pbo`) | **Response 200:** ```json { "success": true, "data": { "message": "Mission 'MyMission.Altis.pbo' deleted" }, "error": null } ``` **Error 404:** Mission file not found. --- ## Mod Endpoints All mod endpoints require the adapter to support the `mod_manager` capability. Returns **400** `NOT_SUPPORTED` if the game type does not support mods. ### GET /servers/{server_id}/mods List all available mods and which are currently enabled for this server. **Auth:** Required (any role) **Response 200:** ```json { "success": true, "data": { "server_id": 1, "enabled_count": 2, "mods": [ { "name": "@CBA_A3", "path": "D:/Arma3Server/1/mods/@CBA_A3", "size_bytes": 12345678, "enabled": true, "is_server_mod": false, "display_name": "Community Base Addons A3", "workshop_id": "450814997" }, { "name": "@ACRE2", "path": "D:/Arma3Server/1/mods/@ACRE2", "size_bytes": 9876543, "enabled": true, "is_server_mod": true, "display_name": "ACRE2", "workshop_id": "751965892" }, { "name": "@USAF", "path": "D:/Arma3Server/1/mods/@USAF", "size_bytes": 55000000, "enabled": false, "is_server_mod": false, "display_name": null, "workshop_id": null } ] }, "error": null } ``` Mod folders are scanned from `{server_data_dir}/{server_id}/mods/@*`. `display_name` is parsed from `mod.cpp`; `workshop_id` from `meta.cpp`. `is_server_mod: true` means the mod is passed via `-serverMod=` instead of `-mod=`. --- ### PUT /servers/{server_id}/mods/enabled Set the list of enabled mods. This **replaces** the entire enabled list — send the complete list every time. The server must be restarted for changes to take effect. **Auth:** Admin required **Request:** ```json { "mods": [ { "name": "@CBA_A3", "is_server_mod": false }, { "name": "@ACRE2", "is_server_mod": true } ] } ``` | Field | Type | Required | Description | |---------------------|---------|----------|-------------| | `mods` | array | Yes | Complete list of mod entries to enable | | `mods[].name` | string | Yes | Mod folder name (must start with `@`) | | `mods[].is_server_mod` | bool | No | `true` → `-serverMod=`, `false` (default) → `-mod=` | **Response 200:** ```json { "success": true, "data": { "message": "Enabled mods updated. Restart the server for changes to take effect.", "enabled_mods": [ { "name": "@CBA_A3", "is_server_mod": false }, { "name": "@ACRE2", "is_server_mod": true } ] }, "error": null } ``` **Error 409** `VERSION_CONFLICT` — Config was modified by another request while updating mods. --- ## WebSocket API ### Connection ``` ws://localhost:8000/ws?token=&server_id=1&server_id=2 ``` **Parameters:** | Parameter | Type | Required | Description | |-------------|-------------------|----------|----------------------------------------------| | `token` | string | Yes | JWT access token | | `server_id` | integer (repeatable) | No | Server IDs to subscribe to. Omit for all servers | **Authentication:** JWT is passed as a query parameter because the browser WebSocket API does not support custom headers. If the token is missing or invalid, the connection is closed with code **4001**. **Welcome message on connect:** ```json { "type": "connected", "data": { "user": "1", "subscriptions": [1, 2] } } ``` If `server_id` is omitted, `subscriptions` will be `"all"`. ### Server-Sent Event Types All events follow this format: ```json { "type": "", "server_id": 1, "data": { ... } } ``` #### server_status Emitted when a server's status changes (starting, running, stopping, stopped, crashed, error). ```json { "type": "server_status", "server_id": 1, "data": { "status": "running", "pid": 12345, "started_at": "2026-04-16T10:00:00Z" } } ``` #### log Emitted for each log line from the server process. ```json { "type": "log", "server_id": 1, "data": { "timestamp": "2026-04-16T10:05:23Z", "level": "info", "message": "BattlEye Server: Initialized (v1.240)" } } ``` #### players Emitted when the player list changes. ```json { "type": "players", "server_id": 1, "data": { "players": [ { "slot_id": "1", "name": "PlayerOne", "ping": 45 } ], "count": 1 } } ``` #### metrics Periodic resource usage update. ```json { "type": "metrics", "server_id": 1, "data": { "cpu_percent": 34.2, "ram_mb": 1850.5, "player_count": 1, "timestamp": "2026-04-16T10:05:25Z" } } ``` ### Subscription Model - Each connection subscribes to zero or more `server_id` values. - Subscribing to `server_id=None` (omitting the parameter) means "all servers." - `broadcast(server_id, message)` delivers to all clients subscribed to that `server_id` plus all global subscribers. - If the event loop is closed, events are silently dropped. --- ## Rate Limiting | Endpoint | Limit | |-----------------------|------------------------------| | `POST /auth/login` | 5 attempts per minute per IP | | All other endpoints | 60 requests per minute per token | Exceeded limits return **429 Too Many Requests**. Implemented via `slowapi` middleware. --- ## Error Codes Reference | Code | HTTP Status | Description | |-----------------------------|-------------|------------------------------------------------------| | `UNAUTHORIZED` | 401 | Missing or invalid token | | `FORBIDDEN` | 403 | Insufficient role (admin required) | | `NOT_FOUND` | 404 | Resource not found | | `NOT_SUPPORTED` | 400 | Adapter lacks required capability | | `CONFLICT` | 409 | Duplicate resource (e.g., username taken) | | `SERVER_ALREADY_RUNNING` | 409 | Start called on running server | | `SERVER_NOT_RUNNING` | 409 | Stop/command on stopped server | | `SERVER_NOT_STOPPED` | 409 | Delete called on running server | | `PORT_IN_USE` | 409 | Game or RCon port already occupied | | `CONFIG_VERSION_CONFLICT` | 409 | Optimistic locking conflict on config update | | `VERSION_CONFLICT` | 409 | Config modified by another request during mod update | | `GAME_TYPE_NOT_FOUND` | 404/400 | No adapter registered for this game type | | `EXE_NOT_ALLOWED` | 400 | Executable not in adapter allowlist | | `INVALID_CONFIG` | 422/400 | Config validation failed (adapter-specific) | | `CONFIG_WRITE_ERROR` | 500 | Config file write failed (disk, permissions) | | `NO_RCON_PASSWORD` | 400 | RCon password not configured | | `RCON_ERROR` | 503 | RCon connection or command failed | | `ADAPTER_ERROR` | 400/500 | Generic adapter error | | `FILE_TOO_LARGE` | 413 | Upload exceeds 500 MB | | `NO_FILENAME` | 400 | No filename in upload request | | `VALIDATION_ERROR` | 400 | General validation failure | | `INTERNAL_ERROR` | 500 | Unexpected server error | --- ## UX Enhancement Endpoints (All Implemented) Endpoints added during the Arma 3 UX Enhancement (Phases 1–5). All are live. ### Phase 1 — Config UI Schema ✅ | Method | Path | Auth | Description | |--------|------|------|-------------| | GET ✅ | `/servers/{server_id}/config/schema` | Bearer | Returns widget hints per field for the frontend config editor | ### Phase 2 — Mission Rotation ✅ | Method | Path | Auth | Description | |--------|------|------|-------------| | GET ✅ | `/servers/{server_id}/missions/rotation` | Bearer | Get current mission rotation list | | PUT ✅ | `/servers/{server_id}/missions/rotation` | Admin | Replace mission rotation (requires `config_version` for optimistic locking) | ### Phase 4 — Player Kick / Ban ✅ | Method | Path | Auth | Description | |--------|------|------|-------------| | POST ✅ | `/servers/{server_id}/players/{slot_id}/kick` | Admin | Kick player by slot ID via RCon; requires `reason` in body | | POST ✅ | `/servers/{server_id}/players/{slot_id}/ban` | Admin | Ban player by slot ID via RCon + DB; requires `reason` and optional `duration_minutes` (`null` = permanent) | ### Phase 5 — Log File Browser ✅ | Method | Path | Auth | Description | |--------|------|------|-------------| | GET ✅ | `/servers/{server_id}/logfiles` | Bearer | List historical `.rpt` log files with `filename`, `size_bytes`, `modified_at` | | GET ✅ | `/servers/{server_id}/logfiles/{filename}/download` | Bearer | Download a historical `.rpt` log file as `text/plain` | | DELETE ✅ | `/servers/{server_id}/logfiles/{filename}` | Admin | Delete a historical `.rpt` log file |