SealSkin

HTTP API

How requests are wrapped and authenticated, and every endpoint the server exposes.

The API is what the served UI and the shells speak; it is documented here for people writing their own client or automation. FastAPI also publishes an OpenAPI description at /openapi.json and an interactive explorer at /docs on the API port, but neither can be used directly because of the encryption envelope described first.

Conventions

Base URL. https://<server>:8443 (through Caddy) or http://<server>:8000 (the API server directly). Both serve the same routes.

Handshake. Before anything else, establish a crypto session:

POST /api/handshake/initiate         -> {"nonce": b64, "signature": b64}
   verify: RSA-PSS(SHA-256, salt 32) over the nonce with the server public key
POST /api/handshake/exchange         {"encrypted_session_key": b64}
   RSA-OAEP(SHA-256) of a 32-byte AES key under the server public key
                                     -> {"session_id": hex}

Envelope. Every endpoint marked encrypted below expects the header X-Session-ID: <session_id> and, for requests with a body, {"iv": b64, "ciphertext": b64} where the ciphertext is AES-256-GCM of the JSON body with a 12-byte IV and no additional data. JSON responses come back in the same envelope. Optionally add X-Idempotency-Key: <random> to a non-GET request to make a retry safe.

Authentication. Endpoints marked user or admin require Authorization: Bearer <jwt>, an RS256 token signed with the user's private key whose claims include sub (the username) and exp. admin endpoints additionally require an administrator account.

Errors. Standard HTTP status codes with a JSON {"detail": ...} body: 400 for a bad envelope or crypto session, 401 for a bad token, 403 for a permission the account lacks, 404, 422 for a body that fails validation, 500 for provider errors.

Handshake and status

Method and pathAuthPurpose
POST /api/handshake/initiatenoneSigned nonce for server verification.
POST /api/handshake/exchangenoneRegister the wrapped AES key; returns the crypto session id.
POST /api/admin/statusencrypted, userThe caller's role, effective settings, detected GPUs, CPU model, disk usage and proxy certificate expiry.
GET /api/ui/versionnone{"version", "bridge"}: server version and bridge protocol version.
GET /api/ui/template_schemanoneThe template editor's variable definitions.

Applications and launching

Method and pathAuthPurpose
POST /api/applicationsencrypted, userApplications the caller may launch, with their capabilities and extensions.
GET /api/app_icon/{app_id}encrypted, userA custom (meta-app) icon as base64 JSON.
POST /api/launch/simpleencrypted, userLaunch with no context. Body: application_id, home_name?, language?, timezone?, selected_gpu?, launch_in_room_mode?, wayland_mode?.
POST /api/launch/urlencrypted, userAs above plus url; the container receives SEALSKIN_URL.
POST /api/launch/fileencrypted, userAs above plus filename, upload_id, total_chunks, open_file_on_launch?; the uploaded file is placed in Desktop/files and, if requested, SEALSKIN_FILE points at it.
POST /api/launch/file_pathencrypted, user, persistent storageLaunch with a file already in the caller's shared files (filename).

A launch returns {"session_url", "session_id"}; session_url is relative to the session port and contains the one-time access token.

Sessions

Method and pathAuthPurpose
GET /api/sessionsencrypted, userThe caller's sessions, newest first.
DELETE /api/sessions/{session_id}encrypted, userStop one of the caller's sessions.
POST /api/sessions/{session_id}/send_fileencrypted, userMove a finished upload (filename, upload_id, total_chunks) into the session's files directory.
GET /{session_id}/access tokenExchange ?access_token= for the session cookie and redirect. Add &embedded=true for a SameSite=None cookie.
GET /room/{session_id}access or collaboration tokenThe collaboration room page.
WS /ws/room/{session_id}collaboration tokenThe room's WebSocket.

Everything under /{session_id}/ other than the initial exchange is proxied by Caddy to the container after forward_auth.

Home directories

Method and pathAuthPurpose
GET /api/homedirsencrypted, user, persistent storageThe caller's home directories.
POST /api/homedirsencrypted, user, persistent storageCreate one (home_name, letters, digits, _, -).
DELETE /api/homedirs/{home_name}encrypted, user, persistent storageDelete one and its contents.

Uploads

Uploads are chunked so that large files fit within the encrypted envelope. Upload ids are lowercase UUIDs scoped to the uploading user.

Method and pathAuthPurpose
POST /api/upload/initiateencrypted, user{"filename", "total_size"}{"upload_id"}.
POST /api/upload/chunkencrypted, user{"upload_id", "chunk_index", "chunk_data_b64"}.
POST /api/upload/to_storageencrypted, user, persistent storageFinalise into the shared files of home_name (filename, upload_id, total_chunks).

Files

All paths are relative to the named home directory (or _sealskin_shared_files).

Method and pathAuthPurpose
GET /api/files/list/{home_dir}?path=&page=&per_page=&search=encrypted, user, persistent storageA page of directory entries.
POST /api/files/create_folder/{home_dir}encrypted, user, persistent storage{"path", "folder_name"}.
POST /api/files/delete/{home_dir}encrypted, user, persistent storage{"paths": [...]} → a task id; deletion runs in the background.
GET /api/files/delete_status/{task_id}encrypted, userProgress of a deletion.
GET /api/files/download/chunk/{home_dir}?path=&chunk_index=encrypted, user, persistent storageOne chunk of a file, base64, with is_last_chunk.
POST /api/files/upload_to_dir/{home_dir}encrypted, user, persistent storageFinalise an upload into a directory (path, filename, upload_id, total_chunks).
POST /api/files/launch_from_storageencrypted, user, persistent storageReturns a launch context for a server-side file; the client opens the launcher with it.

Public shares

Method and pathAuthPurpose
POST /api/files/shareencrypted, user, public sharing{"home_dir", "path", "password"?, "expiry_hours"?} → share info including its URL.
GET /api/files/sharesencrypted, user, public sharingThe caller's shares.
DELETE /api/files/share/{share_id}encrypted, user, public sharingRevoke a share.
GET /public/{share_id}noneThe file, or a password form.
POST /public/{share_id}noneForm field password; redirects to a one-time download URL.
GET /public/download/{token}noneDownload with a one-time token.

Administration

All encrypted, admin.

Method and pathPurpose
POST /api/admin/dataAdmins, users, groups, server public key, ports and GPUs in one call.
POST /api/admin/admins, DELETE /api/admin/admins/{username}Create (username, public_key?) or delete an administrator.
POST /api/admin/usersCreate a user (username, public_key?, settings); returns the generated private key when no key was supplied.
PUT /api/admin/users/{username}Replace a user's settings.
DELETE /api/admin/users/{username}Delete a user and their storage.
GET/POST/DELETE /api/admin/users/{username}/homedirs[/{home}]A user's home directories.
GET/POST/DELETE /api/admin/admins/{username}/homedirs[/{home}]An administrator's home directories.
POST /api/admin/groups, PUT /api/admin/groups/{name}, DELETE /api/admin/groups/{name}Groups.
GET/POST /api/admin/apps/stores, DELETE /api/admin/apps/stores/{name}App stores.
GET /api/admin/apps/available?url=&store_name=&refresh=A store's catalogue, cached unless refresh.
GET /api/admin/apps/installedInstalled apps with image status.
POST /api/admin/apps/installedInstall from a store entry.
PUT /api/admin/apps/installed/{app_id}Replace with a full definition.
PATCH /api/admin/apps/installed/{app_id}Partial update: record fields (users, groups, app_template, auto_update) apply directly, anything else becomes an override.
DELETE /api/admin/apps/installed/{app_id}Uninstall (purges a meta-app's icon and template).
POST /api/admin/apps/installed/{app_id}/check_updateCompare the local image digest with the registry.
POST /api/admin/apps/installed/{app_id}/pull_latestPull the newest image.
POST /api/admin/apps/metaCreate a meta-app (name, base_app_id, logo, autostart scripts, users, groups).
POST /api/admin/launch/meta_customizeLaunch a meta-app with its template mounted read-write.
GET/POST /api/admin/apps/templates, DELETE /api/admin/apps/templates/{name}Templates.
GET /api/admin/sessionsEvery session grouped by user.
DELETE /api/admin/sessions/{session_id}Stop any session.

Internal

GET /internal/resolve_session/{session_id} exists only for Caddy's forward_auth; Caddy refuses it on the public listener and the API server only receives it over loopback. It answers 200 with X-Upstream-Host and X-Upstream-Auth headers, or 403/404.

On this page

Edit on GitHub