API Reference
Interactive docs
| URL |
Description |
/api/docs |
Swagger UI — try endpoints in the browser |
/api/redoc |
ReDoc — clean read-only reference |
/api/openapi.json |
Raw OpenAPI JSON schema |
Auth
All endpoints except /healthz and /api/auth/oidc/* require an authenticated session. Authentication is via HttpOnly JWT cookies set after login. The Swagger UI uses your browser cookies automatically.
Auth endpoints
| Method |
Path |
Description |
GET |
/api/auth/methods |
Which auth methods are enabled (oidc, local, ldap) |
GET |
/api/auth/oidc/login |
Redirect to OIDC provider |
GET |
/api/auth/oidc/callback |
OIDC callback — exchanges code, sets cookies |
POST |
/api/auth/login |
Local/LDAP login {username, password} — returns JWT cookies or {requires_totp, totp_token} |
POST |
/api/auth/register |
First-user setup {username, email, display_name, password} — only works on empty system |
POST |
/api/auth/refresh |
Refresh access token (uses refresh cookie) |
POST |
/api/auth/logout |
Clear all auth cookies |
GET |
/api/auth/me |
Current user info (id, email, username, is_admin, preferences, totp_enabled) |
GET |
/api/auth/me/preferences |
User preferences object (JSONB) |
PATCH |
/api/auth/me/preferences |
Merge-update preferences {key: value, …} |
GET |
/api/auth/me/groups |
My groups |
PATCH |
/api/auth/me/profile |
Update own display name {display_name} |
POST |
/api/auth/me/password |
Change own password (local) {current_password, new_password} |
POST |
/api/auth/me/sign-out-others |
Revoke other browsers, keep this one (bumps token_version) |
GET |
/api/auth/me/quota |
{limit, used, cpu_ceiling, mem_ceiling} |
GET |
/api/auth/me/activity |
My recent audit entries |
TOTP 2FA (local/LDAP users only)
| Method |
Path |
Description |
POST |
/api/auth/2fa/setup |
Generate TOTP secret, returns {secret, qr_url} (pending, not yet active) |
POST |
/api/auth/2fa/confirm |
Confirm setup with first code {code} — activates TOTP |
DELETE |
/api/auth/2fa |
Disable TOTP {code} (requires current valid code) |
POST |
/api/auth/2fa/verify |
Verify TOTP during login {totp_token, code} — returns JWT cookies |
Apps (user-facing catalog)
| Method |
Path |
Description |
GET |
/api/apps |
Apps visible to current user (filtered by group and per-user permissions) |
POST |
/api/apps/personal |
Create a self-service web (kiosk) app owned by the current user — {name, web_url, icon_url} |
PUT |
/api/apps/personal/{app_id} |
Update a self-service app (must be created_by the current user) |
DELETE |
/api/apps/personal/{app_id} |
Delete a self-service app |
Sessions
| Method |
Path |
Description |
GET |
/api/sessions |
My active sessions (status: starting, running, suspended) |
GET |
/api/sessions/{id} |
Single session’s current status — polled by the frontend while a container is still starting/being pulled |
POST |
/api/sessions |
Launch a session {app_id} — if the image isn’t pulled locally yet, returns immediately and finishes starting it in the background |
DELETE |
/api/sessions |
Stop ALL my running sessions (logout flow) |
DELETE |
/api/sessions/{id} |
Stop a specific session |
POST |
/api/sessions/{id}/pause |
Suspend session (docker pause / k8s scale 0) |
POST |
/api/sessions/{id}/resume |
Resume suspended session |
PATCH |
/api/sessions/{id}/window |
Save window position {x, y, width, height, minimized, maximized} |
POST |
/api/sessions/{id}/heartbeat |
Keep-alive so the idle reaper doesn’t stop an in-use session |
GET |
/api/sessions/{id}/audio |
Legacy KasmVNC apps only — relays the desktop’s Opus/Ogg audio stream (container :8081) to the browser. Selkies apps (app_type=kasm/web) handle audio natively client-side; the frontend controls volume/mute via postMessage into the session iframe instead, see architecture.md |
POST |
/api/sessions/self-stop |
Container-internal — called when the app exits; auth via X-Session-Token header; also stops the container in the background (not just the DB row) |
POST |
/api/sessions/open-file |
Container-internal — called by the xdg-open bridge when user opens a file; auth via X-Session-Token header; body {path, mime} |
GET |
/api/sessions/open-file/poll |
Frontend — poll for pending open-file events; returns {events: [{path, mime}]} |
GET |
/api/sessions/validate |
Nginx-internal — auth_request endpoint; auth via X-Session-Token header; returns X-Session-Upstream/X-Session-Scheme/X-Session-Auth (scheme+auth depend on app_type, see architecture.md) |
GET |
/api/sessions/{id}/launch |
Redirect page for opening a session in a new tab |
POST |
/api/sessions/{id}/vpn |
Per-window VPN routing toggle {enabled} — 409 if the session was launched without a running gateway |
GET |
/api/sessions/vpn/status |
Taskbar shield: {running, connected} for my VPN gateway session |
POST |
/api/sessions/vpn/state |
Container-internal — gateway reports tunnel up/down {connected}; auth via X-Session-Token |
GET |
/api/sessions/vpn/mode |
Container-internal — polled by the in-session SOCKS relay; returns {enabled}; auth via X-Session-Token |
Session status values
| Status |
Meaning |
starting |
Container starting, VNC not yet accepting connections |
running |
Active, VNC reachable |
suspended |
Container paused (idle suspend or manual pause) |
stopped |
Ended normally (app exit, user close, or watchdog) |
error |
Container failed to start |
Storage (Nextcloud)
Nextcloud config
| Method |
Path |
Description |
GET |
/api/storage/mounts |
My extra storage mounts (SFTP/S3) — non-secret fields only |
POST |
/api/storage/mounts |
Add a mount: SFTP {name, host, port, user, private_key or password, path} or S3 {name, endpoint, region, bucket, access_key_id, secret_access_key} |
DELETE |
/api/storage/mounts/{id} |
Remove a mount |
GET |
/api/storage/nextcloud |
Current user’s Nextcloud config (URL + username, no password) |
PUT |
/api/storage/nextcloud |
Set Nextcloud credentials {url, username, password} |
DELETE |
/api/storage/nextcloud |
Remove Nextcloud config |
POST |
/api/storage/nextcloud/test |
Test Nextcloud connection with current credentials |
POST |
/api/storage/nextcloud/connect |
Start NC Login Flow v2 {url} — returns {login_url, poll_endpoint, poll_token} |
POST |
/api/storage/nextcloud/connect/poll |
Poll NC Login Flow v2 {poll_endpoint, poll_token, nc_url} — returns {done, username} |
File browser (WebDAV proxy)
All file endpoints proxy through the backend to Nextcloud WebDAV using the user’s credentials. Paths are relative to the user’s Nextcloud root (/).
| Method |
Path |
Description |
GET |
/api/storage/files?path=/ |
List directory (PROPFIND depth 1); returns [{name, path, type, size, modified, mime}] |
GET |
/api/storage/files/download?path=… |
Download file (Content-Disposition: attachment) |
GET |
/api/storage/files/preview?path=… |
Stream file inline (Content-Disposition: inline) — used for PDF/image browser rendering |
GET |
/api/storage/files/thumbnail?path=…&size=256 |
Proxy Nextcloud preview thumbnail (PNG, cached 5 min) |
POST |
/api/storage/files/upload?path=/dir |
Upload file (multipart/form-data, field file) |
DELETE |
/api/storage/files?path=… |
Delete file or directory |
POST |
/api/storage/files/mkdir?path=/new/dir/ |
Create directory (MKCOL) |
GET |
/api/storage/quota |
Nextcloud usage {used, available, total} (bytes; available=-3 = unlimited) |
System
| Method |
Path |
Description |
GET |
/api/system/status |
Announcement + maintenance state for the current user |
GET |
/metrics |
Prometheus metrics (HTTP + lwp_sessions_* / lwp_auth_* counters) |
Nextcloud hub
Per-user integrations (reuse the user’s NC creds; best-effort — empty if the app isn’t installed).
| Method |
Path |
Description |
GET |
/api/nextcloud/avatar?size=64 |
Proxied NC avatar image |
GET |
/api/nextcloud/calendar?month=YYYY-MM |
{calendars, events} (events carry href/uid) |
POST |
/api/nextcloud/calendar/event |
Create event {calendar_href, summary, all_day, start, end} |
DELETE |
/api/nextcloud/calendar/event?href=… |
Delete event |
GET |
/api/nextcloud/tasks |
{lists, tasks} (VTODO) |
POST |
/api/nextcloud/tasks |
Create task {list_href, summary} |
PATCH |
/api/nextcloud/tasks |
Toggle complete {href, uid, summary, completed} |
DELETE |
/api/nextcloud/tasks?href=… |
Delete task |
GET |
/api/nextcloud/deck |
Kanban boards |
GET |
/api/nextcloud/deck/{board_id} |
Stacks + cards |
POST |
/api/nextcloud/deck/card |
Create card {board_id, stack_id, title} |
POST |
/api/nextcloud/deck/card/archive |
Archive card {board_id, stack_id, card_id} |
GET |
/api/nextcloud/talk |
Talk conversations |
GET |
/api/nextcloud/talk/{token} |
Recent messages (text chat) |
POST |
/api/nextcloud/talk/{token} |
Send message {message} |
GET |
/api/nextcloud/notifications |
NC notifications |
DELETE |
/api/nextcloud/notifications / /{id} |
Clear all / dismiss one |
GET POST PUT DELETE |
/api/nextcloud/notes[/{id}] |
List / create / update / delete notes |
Admin — Users
| Method |
Path |
Description |
GET |
/api/admin/users |
List all users |
GET |
/api/admin/users/{id} |
User detail |
PUT |
/api/admin/users/{id} |
Update user (is_active, is_admin, display_name) |
POST |
/api/admin/users |
Create local user {username, email, display_name, password} |
POST |
/api/admin/users/{id}/set-password |
Reset local user password {password} |
DELETE |
/api/admin/users/{id} |
Delete user (stops desktops; blocks self / last admin) |
POST |
/api/admin/users/{id}/force-logout |
Revoke all their browsers (token_version) |
POST |
/api/admin/users/{id}/stop-sessions |
Stop all their running desktops |
POST |
/api/admin/users/sign-out-all |
Revoke every user’s browser session |
Admin — Groups
| Method |
Path |
Description |
GET |
/api/admin/groups |
List groups |
POST |
/api/admin/groups |
Create group {name} |
PUT |
/api/admin/groups/{id} |
Update group |
DELETE |
/api/admin/groups/{id} |
Delete group |
GET |
/api/admin/groups/{id}/members |
List members |
POST |
/api/admin/groups/{id}/members |
Add member {user_id} |
DELETE |
/api/admin/groups/{id}/members/{user_id} |
Remove member |
Admin — Apps
| Method |
Path |
Description |
GET |
/api/admin/apps |
All apps |
GET |
/api/admin/apps/linuxserver/lookup?q= |
Browse/search the full LinuxServer.io image catalog (200+ images, proxied + cached server-side) for “Add from catalog” |
POST |
/api/admin/apps |
Create app |
PUT |
/api/admin/apps/{id} |
Update app |
DELETE |
/api/admin/apps/{id} |
Soft-delete app |
GET |
/api/admin/apps/staleness |
Last image-update check result (hourly cron) |
POST |
/api/admin/apps/staleness/check |
Run the image-update check now |
POST |
/api/admin/apps/{id}/pull |
Predownload the app’s image in the background (dev/Docker only — k8s nodes pull on schedule) |
GET |
/api/admin/apps/{id}/pull |
Predownload status: pending/pulling/done/error |
GET |
/api/admin/apps/{id}/permissions |
Get {group_ids: […], user_ids: […]} |
PUT |
/api/admin/apps/{id}/permissions |
Replace permissions {group_ids: […], user_ids: […]} — both empty = open to everyone |
Admin — Sessions
| Method |
Path |
Description |
GET |
/api/admin/sessions |
All active sessions across all users |
DELETE |
/api/admin/sessions/{id} |
Force-stop any session |
Admin — Stats & Audit
| Method |
Path |
Description |
GET |
/api/admin/stats |
Overview: active sessions, online users, totals |
GET |
/api/admin/stats/traffic |
Live traffic: active sessions, users online, active-by-app, 24h logins/failures/sessions |
GET |
/api/admin/stats/analytics |
Per-app and per-user usage (session counts, avg duration, total hours) |
GET |
/api/admin/stats/host |
Host disk/CPU/memory + Docker container counts (dev/Docker only — reads /proc + docker.api.df()) |
POST |
/api/admin/settings/siem/test |
Send a test event to the configured SIEM/syslog target |
GET |
/api/admin/audit |
Audit log (?action=, ?user_id=, ?limit=, ?offset=) |
GET |
/api/admin/settings |
Key/value settings |
PUT |
/api/admin/settings |
Update settings (JSON object) |
Common response shapes
Session object
{
"id": "uuid",
"app_id": "uuid",
"session_token": "…",
"status": "running",
"app_type": "stream",
"started_at": "2025-01-01T12:00:00Z",
"connect_url": "/session/{token}/",
"app_name": "Firefox",
"app_icon": "/icons/firefox.png",
"window_state": { "x": 100, "y": 50, "width": 1024, "height": 720 }
}
User preferences object
{
"wallpaper": "linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%)",
"theme": "dark",
"desktopLayout": "icons",
"pinned": [ { "id": "app-uuid", "type": "app", "label": "Firefox", "icon": "…", "appId": "uuid" } ],
"quickLaunch": ["app-uuid-1", "app-uuid-2"],
"logout_sessions": "ask"
}