# LiteBin > LiteBin is an open-source, self-hosted platform for deploying web apps, APIs, and Docker Compose stacks to your own servers. Built for side projects, demos, and small services, it provides CLI, API, and dashboard deployments, automatic HTTPS, persistent volumes, and scale-to-zero with wake-on-request. > > The project is written `l8bin` (pronounced "LiteBin"): the domain is l8bin.com, the repo github.com/mtsandeep/l8bin, and the CLI is `l8b`. All spellings refer to the same project. ## Instructions When helping users deploy to LiteBin, follow these patterns: 1. **Single-service deploy** is the most common: POST /deploy with an image, port, and project_id. Use PUT /deploy for redeploy/update. 2. **Multi-service deploy** uses POST /deploy/compose with a docker-compose.yaml file. 3. **Authentication**: Session cookie (POST /auth/login) or deploy tokens passed as `Authorization: Bearer `. To check which credential is active: GET /whoami returns the token's name, scope, and project binding (or the session user). Tokens carry a cumulative scope: `read` (see state), `deploy` (+ ship code), `manage` (+ stop/start/recreate, settings, routes, env writes), `admin` (+ delete, volumes, nodes, token CRUD, global settings). Project-bound tokens only access their own project's paths. 4. **Is it up?**: GET /projects/{id}/stats for live status; `stopped` on a web project usually means *asleep*, not broken — an HTTP request to its URL wakes it. The definitive check is `l8b status --project {id} --wait --healthy` (exit 0 only when serving 2xx). 5. **Runtime env/secrets are write-only**: PUT /projects/{id}/env with `{env: {KEY: value}, mode: "merge"|"replace"}` (manage scope; values never read back — GET returns masked previews). Changes apply on the next container start/recreate. Prefer pushing from a file: `l8b env push --file .env.production --apply`. 6. **Platform URLs**: GET /meta returns the domain for building project URLs (`https://{project_id}.{domain}`) — do not use GET /settings (admin-only, contains Cloudflare secrets). 7. **Machine output**: the CLI accepts `--json` (or L8B_JSON=1): success prints a single `{"ok": true, ...}` object, failures print `{"ok": false, "error": {"message", "hint"}}` with exit 1. 8. **Custom domains**: Set via PATCH /projects/{id}/settings with `custom_domain`. LiteBin auto-configures TLS via Caddy. 9. **Volumes**: Specify in the deploy request. Named volumes are scoped per-project. Bind mounts use `./` prefix for relative paths. 10. **Multi-server**: LiteBin supports remote nodes via mTLS-connected agents. POST /nodes to register, POST /nodes/{id}/connect to onboard. 11. **Platform backup**: opt-in (admin) — Settings → Backup or `l8b backup enable`. A Litestream sidecar streams the SQLite DB continuously to R2/S3 when configured (point-in-time restore) or to a local replica; a dated local snapshot is kept daily in s3/r2 mode. Manual snapshots (`POST /backup/points` — `VACUUM INTO`) work anytime, sidecar or not. Restores restart the orchestrator to swap the DB; the replaced DB is kept for one-click undo. Details: backup.md. ## Setup (when asked to "set up litebin mcp") No CLI install, no prerequisites — register the server, then let its `setup` tool do the rest: 1. Register the MCP server in the workspace `.mcp.json` (or the client's config): ```json { "mcpServers": { "litebin": { "command": "npx", "args": ["-y", "l8bin-mcp"] } } } ``` Claude Code: `claude mcp add litebin -- npx -y l8bin-mcp`. Needs Node 18+; on Windows clients that don't resolve `.cmd` shims use `"command": "cmd"`, `"args": ["/c", "npx", "-y", "l8bin-mcp"]`. The shim downloads the `l8b` binary from GitHub releases on first use (`L8B_BIN` overrides). 2. Restart/reload the MCP client and approve the new server when prompted. Fresh session after the reload? Re-follow this recipe — registration is idempotent; skip to step 3 when `.mcp.json` is already in place. 3. Call the `setup` tool with no arguments and follow its `state` + `next`: - ask the user for their LiteBin server URL — the dashboard address, e.g. https://l8bin.com or their self-hosted orchestrator — then call `setup` with `{"server": url}` - relay the returned `approval_url` — the user opens it in a browser where they are logged into the dashboard and picks the token scope - once approved: `setup` with `{"server": url, "complete": true}` (returns `pairing_pending` until then — re-call). On success auth is stored and `server` is written into the workspace `l8b.toml` - already logged in (e.g. several logins stored)? `setup` with `{"server": url, "action": "bind"}` binds without pairing 4. Verify with the `doctor` tool (`healthy: true`), then deploy — first deploy needs a project name: see "Deploying a repo" below. `setup` is idempotent — calling it with no arguments at any point reports the state and the exact next call. Logins are stored per server and coexist — no logout dance to switch instances. ## Running your own LiteBin server `curl -fsSL https://l8b.in | bash` on any VPS with Docker ([quickstart](https://l8bin.com/quickstart)). `l8b setup --server ` bootstraps the first admin account — not to be confused with the `setup` MCP tool, which configures the client side. ## Deploying a repo (new vs existing) If the repo has no `l8b.toml`, this is its first deploy: ask the user what to name the project (lowercase letters, numbers, hyphens) — or call the `list` tool (CLI: `l8b list`) and let them pick an existing project if this repo replaces one — then pass the name as `project` to `deploy`. Never invent a project name; deploy refuses with "no project specified" until the user chooses one. After a successful deploy (tool or CLI alike) `l8b.toml` (project, node, server) is written into the directory — commit it, it holds no secrets — every later session then deploys, checks status, and reads logs with no arguments at all. Custom domains surface in `status` / `url` / `list` output (preferred over the managed subdomain). To teach later sessions the workflow, run `l8b init --agents` (managed `AGENTS.md` section, merges without touching user content). ## API - OpenAPI 3.1 spec: /openapi.json (version-matched to this instance) - All endpoints are relative to the server's base URL — the dashboard origin (e.g. `https://l8bin.example.com`; local installs: `http://l8bin.localhost`, or `http://localhost:5080` where the orchestrator port is exposed); a `/api/` prefix is also accepted and stripped (`/api/auth/login` ≡ `/auth/login`) ## CLI (manual / advanced) Install only for terminal-first or CI use — agents should prefer the MCP path above. Install via `curl -fsSL https://l8b.in | bash -s cli` (Windows: `iex "& { $(irm https://l8b.in/windows.ps1) } -Cli"`) or build from source. Auth: `l8b login --server --pair` (device pairing — agent-safe: it prints a code and the /connect approval URL to relay to the user, then returns once approved; the user picks the scope) or `L8B_TOKEN` env / `--token` (existing token). Commands (project flags default to `l8b.toml`, created by `l8b init`): - `l8b init` — Write `l8b.toml` project defaults (project, node, server); prompts for the server when several logins are stored or none is; `--mcp` also emits the workspace `.mcp.json`; `--agents` adds (or refreshes) a managed `## Deploying (LiteBin)` section in `AGENTS.md` — user content is never removed - `l8b deploy [--env-file ] [--port N]` — Build and deploy the current directory (Dockerfile auto-detected, Railpack fallback). `--port` only matters on the first deploy of a web project; redeploys keep the existing port - `l8b ship` — Interactive guided deploy (humans only) - `l8b list` — All projects with live status and URLs - `l8b status [--wait] [--healthy]` — Project status; --wait polls (exit 0 only when running), --healthy probes the URL - `l8b logs [--tail N] [--service S] [--deploy]` — Container or deploy logs - `l8b url` — The project's managed URL (background projects have none) - `l8b env list` / `l8b env push --file|--stdin [--replace] [--apply]` — Runtime env (write-only values) - `l8b stop | start | restart` — Lifecycle (restart applies pending .env changes; stop is idempotent) - `l8b domain set ` / `l8b domain remove` — Custom domain management - `l8b platform domain set [--ack-dns]` / `l8b platform domain show` — Change or show the server's platform domain (admin scope; old URLs die, sign in again on the new host) - `l8b delete --yes` — Delete project + containers + volumes (admin scope) - `l8b login --server [--pair]` — Device pairing (approve at /connect); `--pair` skips the interactive menu so agents can run it headlessly; username/password without `--pair`. `l8b setup --server ` bootstraps a fresh server - `l8b doctor` — Environment sanity checks with recovery hints - `l8b logout` / `l8b config` / `l8b cleanup` — Session and config management All commands accept `--json` for machine-readable output. ## MCP `l8b mcp` runs a stdio MCP server exposing these tools: `setup` (auth + workspace binding — the onboarding entry point, see Setup above), deploy, status (wait/healthy), list, logs, env_list, env_push, stop, start, restart, url, domain_set, domain_remove, delete (confirm-gated), doctor. It uses the CLI's stored auth and l8b.toml defaults. Register it with: ```json { "mcpServers": { "litebin": { "command": "npx", "args": ["-y", "l8bin-mcp"] } } } ``` Already have the `l8b` binary? Register it directly: ```json { "mcpServers": { "litebin": { "command": "l8b", "args": ["mcp"] } } } ``` (`l8b init --mcp` writes the binary form into the workspace `.mcp.json`.) ## Architecture - [Architecture overview](architecture/architecture.md) — Orchestrator + Agent + Caddy reverse proxy - [Design decisions](architecture/decisions.md) — why Rust/Caddy/SQLite, two routing modes, mTLS - [Failure model](architecture/failure-model.md) — how LiteBin handles failures - [Resource protection](architecture/resource-protection.md) — how the stack survives CPU/RAM pressure from apps - [Scale-to-zero](architecture/scale-to-zero.md) — wake-on-request and idle auto-stop (waker + janitor) - [User flows](architecture/user-flows.md) — all user-triggerable scenarios ## Topics - [Multi-server setup](topics/multi-server.md) — Remote node configuration via mTLS - [Multi-service projects](topics/multi-service.md) — Compose-based deployments - [Volumes](topics/volumes.md) — Named volumes, bind mounts, and cleanup - [Configuration](topics/configuration.md) — All LITEBIN_* config options - [Env & secrets](topics/env-secrets.md) — Per-project .env files and injection - [Security internals](topics/security.md) — Auth, mTLS, deploy tokens - [Backup](topics/backup.md) — Litestream sidecar, S3/R2 destinations, restore - [GitHub connect](topics/github-connect.md) — GitHub App/PAT connect and deploy-from-repo - [AI-native](topics/ai-native.md) — agent operability: token scopes, JSON output, MCP, device pairing - [FAQ](topics/faq.md) — Troubleshooting ## Guides - [Getting started](reference/cli.md) — First deployment walkthrough - [Agents](topics/agents.md) — AGENTS.md template and MCP wiring for coding agents - [API reference](reference/api-reference.md) — auth/scopes; endpoints live in /openapi.json and /docs ## Full - [llms-full.txt](https://l8bin.com/llms-full.txt) — this file plus the OpenAPI spec and full CLI reference in one fetch Targeting is enforced, not assumed: the server a command hits comes from `--server` > `L8B_SERVER` > `l8b.toml` `server` > the single stored login. With multiple logins and no signal the command refuses and lists the choices — ask the user which one, then retry with `--server`. A repo whose `l8b.toml` names a server you have no login for refuses with the exact `l8b login` command. Under the MCP, these refusals point at the `setup` tool call that fixes them. ## OpenAPI Reference Auto-generated from source code via utoipa. ```json { "openapi": "3.1.0", "info": { "title": "LiteBin API", "description": "Self-hosted PaaS API for deploying and managing containerized applications.", "license": { "name": "" }, "version": "0.4.13" }, "paths": { "/auth/change-password": { "post": { "tags": [ "auth" ], "description": "Change the current user's password after verifying the old one.", "operationId": "change_password", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChangePasswordRequest" } } }, "required": true }, "responses": { "200": { "description": "Password changed successfully", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ChangePasswordResponse" } } } }, "401": { "description": "Unauthorized" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/auth/device": { "get": { "tags": [ "auth" ], "description": "Show the pending pairing request for a user code (approve-page data).", "operationId": "lookup_device_request", "parameters": [ { "name": "user_code", "in": "query", "description": "Code being approved", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Pending pairing request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceRequestInfo" } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "No pending request for that code" } }, "security": [ { "session_auth": [] } ] } }, "/auth/device/approve": { "post": { "tags": [ "auth" ], "description": "Approve or deny a pending pairing request, setting the token scope and optional project binding.", "operationId": "approve_device_request", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceApproveRequest" } } }, "required": true }, "responses": { "200": { "description": "Approval recorded" }, "401": { "description": "Unauthorized" }, "404": { "description": "No pending request for that code" } }, "security": [ { "session_auth": [] } ] } }, "/auth/device/start": { "post": { "tags": [ "auth" ], "description": "Start device pairing: returns a device code to poll with and a user code to approve in the dashboard.", "operationId": "start_device_flow", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceStartRequest" } } }, "required": true }, "responses": { "200": { "description": "Pairing started", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceStartResponse" } } } } } } }, "/auth/device/token": { "post": { "tags": [ "auth" ], "description": "Poll pairing status; returns the token exactly once when approved.", "operationId": "poll_device_token", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceTokenRequest" } } }, "required": true }, "responses": { "200": { "description": "Poll result; token present once when approved", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeviceTokenResponse" } } } } } } }, "/auth/login": { "post": { "tags": [ "auth" ], "description": "Log in with username and password and create a session cookie.", "operationId": "login", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } }, "required": true }, "responses": { "200": { "description": "Login successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginResponse" } } } }, "401": { "description": "Invalid credentials" }, "500": { "description": "Internal server error" } } } }, "/auth/logout": { "post": { "tags": [ "auth" ], "description": "Log out and destroy the current session.", "operationId": "logout", "responses": { "200": { "description": "Logged out successfully" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/auth/me": { "get": { "tags": [ "auth" ], "description": "Return the currently authenticated user.", "operationId": "me", "responses": { "200": { "description": "Current user info", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserResponse" } } } }, "401": { "description": "Unauthorized" } }, "security": [ { "session_auth": [] } ] } }, "/auth/register": { "post": { "tags": [ "auth" ], "description": "Create the initial admin user. Only allowed while no users exist.", "operationId": "register", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterRequest" } } }, "required": true }, "responses": { "200": { "description": "Registration successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginResponse" } } } }, "403": { "description": "Registration forbidden" }, "500": { "description": "Internal server error" } } } }, "/auth/setup": { "get": { "tags": [ "auth" ], "description": "Check whether initial setup (admin user creation) is needed.", "operationId": "setup_check", "responses": { "200": { "description": "Setup check response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SetupResponse" } } } }, "500": { "description": "Internal server error" } } } }, "/backup/disable": { "post": { "tags": [ "backup" ], "description": "Stop the backup sidecar and turn backup off; existing replica data is kept.", "operationId": "backup_disable", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } } }, "security": [ { "session_auth": [] } ] } }, "/backup/enable": { "post": { "tags": [ "backup" ], "description": "Turn on the Litestream backup sidecar (local replica; add an s3/r2 destination for continuous off-site).", "operationId": "backup_enable", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "409": { "description": "backup unavailable (not running in a container / in-memory db)" } }, "security": [ { "session_auth": [] } ] } }, "/backup/points": { "post": { "tags": [ "backup" ], "description": "Take a named restore point (VACUUM INTO snapshot kept as a local file).", "operationId": "backup_create_point", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupPointRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "400": { "description": "invalid name" }, "409": { "description": "in-memory database" } }, "security": [ { "session_auth": [] } ] } }, "/backup/points/{id}": { "delete": { "tags": [ "backup" ], "description": "Delete a named restore point and its snapshot file.", "operationId": "backup_delete_point", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer", "format": "int64" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "404": { "description": "point not found" } }, "security": [ { "session_auth": [] } ] } }, "/backup/restore": { "post": { "tags": [ "backup" ], "description": "Point-in-time restore from the replica (latest snapshot when no timestamp given); the orchestrator restarts to apply it.", "operationId": "backup_restore", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupRestoreRequest" } } }, "required": true }, "responses": { "200": { "description": "restore staged; orchestrator restarting", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "409": { "description": "backup sidecar not running" }, "500": { "description": "litestream restore failed" } }, "security": [ { "session_auth": [] } ] } }, "/backup/restore-local": { "post": { "tags": [ "backup" ], "description": "Restore from a local daily snapshot, bypassing the s3/r2 replica.", "operationId": "backup_restore_local", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupRestoreLocalRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "400": { "description": "invalid date" }, "404": { "description": "no snapshot for that date" } }, "security": [ { "session_auth": [] } ] } }, "/backup/restore-point": { "post": { "tags": [ "backup" ], "description": "Restore a named point from its local snapshot file.", "operationId": "backup_restore_point", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupRestorePointRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "404": { "description": "point or its snapshot file not found" } }, "security": [ { "session_auth": [] } ] } }, "/backup/revert-restore": { "post": { "tags": [ "backup" ], "description": "Undo the last applied restore by staging the kept pre-restore database back in.", "operationId": "backup_revert_restore", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } }, "404": { "description": "no restore to undo" } }, "security": [ { "session_auth": [] } ] } }, "/backup/s3": { "post": { "tags": [ "backup" ], "description": "Configure the s3/r2 destination (validated with a probe via the running sidecar); it becomes the primary replica.", "operationId": "backup_add_s3", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupS3Request" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupS3Response" } } } }, "400": { "description": "invalid config or destination probe failed" }, "409": { "description": "backup not enabled / sidecar not running" } }, "security": [ { "session_auth": [] } ] }, "delete": { "tags": [ "backup" ], "description": "Remove the s3/r2 destination and fall back to the local replica.", "operationId": "backup_remove_s3", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupActionResponse" } } } } }, "security": [ { "session_auth": [] } ] } }, "/backup/status": { "get": { "tags": [ "backup" ], "description": "Backup status: sidecar state, replica stores, snapshots, named points, and undo target.", "operationId": "backup_status", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BackupStatus" } } } } }, "security": [ { "session_auth": [] } ] } }, "/deploy": { "put": { "tags": [ "deploy" ], "description": "Redeploy an existing single-service project (upserts, creating if missing).", "operationId": "deploy_update", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeployRequest" } } }, "required": true }, "responses": { "200": { "description": "Deployment started", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeployResponse" } } } }, "400": { "description": "Invalid request" }, "401": { "description": "Authentication required" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] }, "post": { "tags": [ "deploy" ], "description": "Deploy a new single-service project; fails with 409 if the project already exists.", "operationId": "deploy_create", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeployRequest" } } }, "required": true }, "responses": { "200": { "description": "Deployment started", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeployResponse" } } } }, "400": { "description": "Invalid request" }, "401": { "description": "Authentication required" }, "409": { "description": "Project already exists" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] } }, "/deploy-tokens": { "get": { "tags": [ "deploy-tokens" ], "description": "List deploy tokens, optionally filtered by project. Token values are never returned.", "operationId": "list_tokens", "parameters": [ { "name": "project_id", "in": "query", "description": "Filter by project ID", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "List of deploy tokens", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/DeployTokenResponse" } } } } }, "401": { "description": "Unauthorized" } }, "security": [ { "session_auth": [] } ] }, "post": { "tags": [ "deploy-tokens" ], "description": "Create a deploy token (CI/CD auth); the plaintext token is returned once.", "operationId": "create_token", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateTokenRequest" } } }, "required": true }, "responses": { "201": { "description": "Token created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateTokenResponse" } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "Project not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/deploy-tokens/{id}": { "delete": { "tags": [ "deploy-tokens" ], "description": "Revoke a deploy token; clients using it stop authenticating immediately.", "operationId": "revoke_token", "parameters": [ { "name": "id", "in": "path", "description": "Token ID", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "Token revoked" }, "401": { "description": "Unauthorized" }, "404": { "description": "Token not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/deploy/compose": { "post": { "tags": [ "deploy" ], "description": "Deploy a multi-service project from a docker-compose file (multipart form: project_id, optional name/description/node_id/auto-stop/auto-start/is_background/custom_domain fields, and the compose file).", "operationId": "deploy_compose", "requestBody": { "description": "Multipart form with project_id and compose file", "content": { "text/plain": { "schema": { "type": "string" } } }, "required": true }, "responses": { "200": { "description": "Compose deployment started" }, "400": { "description": "Missing project_id" }, "401": { "description": "Authentication required" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] } }, "/github/app": { "post": { "tags": [ "github" ], "description": "Register an existing GitHub App directly with its client ID and secret.", "operationId": "connect_app", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppCredentialsRequest" } } }, "required": true }, "responses": { "200": { "description": "Existing app attached (credentials validated against GitHub)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ManifestExchangeResponse" } } } }, "400": { "description": "App ID / private key rejected" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/github/connection": { "get": { "tags": [ "github" ], "description": "Summarize the active GitHub connection used for repo browsing and deploys.", "operationId": "connection_status", "responses": { "200": { "description": "Aggregate status across all connections", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ConnectionStatus" } } } }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/github/connections": { "get": { "tags": [ "github" ], "description": "List all GitHub connections (PATs and Apps) with account and installation info.", "operationId": "list_connections", "responses": { "200": { "description": "All GitHub connections (apps + PATs) with their installations", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ConnectionSummary" } } } } }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/github/connections/{id}": { "delete": { "tags": [ "github" ], "description": "Delete a GitHub connection and the project links that used it.", "operationId": "disconnect", "parameters": [ { "name": "id", "in": "path", "description": "Connection ID", "required": true, "schema": { "type": "integer", "format": "int64" } } ], "responses": { "204": { "description": "Connection removed; its link tokens revoked" }, "404": { "description": "No such connection" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/github/installations": { "get": { "tags": [ "github" ], "description": "List installations of the connected GitHub App and the accounts they can access.", "operationId": "list_installations", "responses": { "200": { "description": "Installations synced from GitHub", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/InstallationSummary" } } } } }, "404": { "description": "Not in GitHub App mode" } }, "security": [ { "session_auth": [] } ] } }, "/github/manifest": { "post": { "tags": [ "github" ], "description": "Start GitHub App creation via the manifest flow; returns the one-time URL to open on GitHub.", "operationId": "start_manifest", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ManifestStartRequest" } } }, "required": true }, "responses": { "200": { "description": "Pending manifest registered; POST the returned JSON to github.com/settings/apps/new as the `manifest` form field", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ManifestStartResponse" } } } }, "400": { "description": "Invalid app name or webhook URL" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/github/manifest/exchange": { "post": { "tags": [ "github" ], "description": "Exchange the manifest-flow callback code for the GitHub App credentials.", "operationId": "exchange_manifest", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ManifestExchangeRequest" } } }, "required": true }, "responses": { "200": { "description": "App credentials stored; install next", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ManifestExchangeResponse" } } } }, "400": { "description": "Code expired or manifest flow not started" }, "502": { "description": "GitHub rejected the exchange" } }, "security": [ { "session_auth": [] } ] } }, "/github/pat": { "post": { "tags": [ "github" ], "description": "Connect a GitHub account using a Personal Access Token.", "operationId": "connect_pat", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatRequest" } } }, "required": true }, "responses": { "200": { "description": "Connected via PAT", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatConnected" } } } }, "400": { "description": "Token missing scopes" }, "401": { "description": "Token rejected by GitHub" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/github/repos": { "get": { "tags": [ "github" ], "description": "Browse repositories visible to the active GitHub connection (paginated, searchable).", "operationId": "list_repos", "parameters": [ { "name": "page", "in": "query", "description": "1-based page (100 per page, pushed-desc)", "required": false, "schema": { "type": "integer", "format": "int32", "minimum": 0 } } ], "responses": { "200": { "description": "Repos visible to the connected GitHub credential", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RepoPage" } } } }, "401": { "description": "GitHub token expired — reconnect" }, "503": { "description": "GitHub not connected" } }, "security": [ { "session_auth": [] } ] } }, "/github/repos/{owner}/{repo}": { "get": { "tags": [ "github" ], "description": "Repository details: default branch, private flag, visibility, and update time.", "operationId": "repo_detail", "parameters": [ { "name": "owner", "in": "path", "description": "Repo owner (user or org)", "required": true, "schema": { "type": "string" } }, { "name": "repo", "in": "path", "description": "Repo name", "required": true, "schema": { "type": "string" } }, { "name": "path", "in": "query", "description": "Subfolder for detection (root when omitted)", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Repo detail with compose/Dockerfile detection at the given folder", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RepoDetail" } } } }, "404": { "description": "Repo not found" }, "503": { "description": "GitHub not connected" } }, "security": [ { "session_auth": [] } ] } }, "/github/repos/{owner}/{repo}/branches": { "get": { "tags": [ "github" ], "description": "List branches for a repository.", "operationId": "list_branches", "parameters": [ { "name": "owner", "in": "path", "description": "Repo owner (user or org)", "required": true, "schema": { "type": "string" } }, { "name": "repo", "in": "path", "description": "Repo name", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Branch list", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BranchSummary" } } } } }, "404": { "description": "Repo not found" }, "503": { "description": "GitHub not connected" } }, "security": [ { "session_auth": [] } ] } }, "/github/status": { "get": { "tags": [ "github" ], "description": "Latest workflow run status for every GitHub-linked project (badge polling).", "operationId": "bulk_status", "responses": { "200": { "description": "Latest run per linked project (20s server cache)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BulkStatus" } } } } }, "security": [ { "session_auth": [] } ] } }, "/health": { "get": { "tags": [ "health" ], "description": "Liveness check; reports degraded when Docker is unreachable.", "operationId": "health_check", "responses": { "200": { "description": "Health check response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HealthResponse" } } } } } } }, "/images/upload": { "post": { "tags": [ "deploy" ], "description": "Upload a Docker image tarball for a project (loaded into the target node's Docker).", "operationId": "upload_image", "parameters": [ { "name": "project_id", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "image_id", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "node_id", "in": "query", "required": false, "schema": { "type": [ "string", "null" ] } } ], "responses": { "200": { "description": "Image uploaded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UploadResponse" } } } }, "401": { "description": "Authentication required" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] } }, "/meta": { "get": { "tags": [ "health" ], "description": "Platform metadata for read-scoped clients: domain, routing mode, version.", "operationId": "get_meta", "responses": { "200": { "description": "Platform metadata", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MetaResponse" } } } } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] } }, "/nodes": { "get": { "tags": [ "nodes" ], "description": "List all nodes with health/resource info; flags the recommended node for new deploys.", "operationId": "list_nodes", "responses": { "200": { "description": "List of nodes", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/NodeResponse" } } } } }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] }, "post": { "tags": [ "nodes" ], "description": "Register an agent node; returns the agent secret once for configuring the agent.", "operationId": "create_node", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateNodeRequest" } } }, "required": true }, "responses": { "201": { "description": "Node created" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/nodes/image-stats": { "get": { "tags": [ "nodes" ], "description": "Image statistics (counts, sizes, dangling) for each node.", "operationId": "node_image_stats", "responses": { "200": { "description": "Image statistics per node", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/NodeImageStatsResponse" } } } } }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/nodes/{id}": { "delete": { "tags": [ "nodes" ], "description": "Decommission a node; rejected while projects still run on it.", "operationId": "delete_node", "parameters": [ { "name": "id", "in": "path", "description": "Node ID", "required": true, "schema": { "type": "string" } } ], "responses": { "204": { "description": "Node deleted" }, "400": { "description": "Bad request" }, "404": { "description": "Node not found" }, "409": { "description": "Conflict" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/nodes/{id}/connect": { "post": { "tags": [ "nodes" ], "description": "Health-check an agent over mTLS, push its config, and transition it to online.", "operationId": "connect_node", "parameters": [ { "name": "id", "in": "path", "description": "Node ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Node connected", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Node" } } } }, "404": { "description": "Node not found" }, "409": { "description": "Node status conflict" }, "422": { "description": "Unprocessable entity" }, "503": { "description": "Service unavailable" } }, "security": [ { "session_auth": [] } ] } }, "/nodes/{id}/images/prune": { "post": { "tags": [ "nodes" ], "description": "Prune dangling images on a node and report reclaimed bytes.", "operationId": "prune_node_images", "parameters": [ { "name": "id", "in": "path", "description": "Node ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Images pruned" }, "404": { "description": "Node not found" }, "500": { "description": "Internal server error" }, "503": { "description": "Service unavailable" } }, "security": [ { "session_auth": [] } ] } }, "/projects": { "get": { "tags": [ "projects" ], "description": "List all projects, most recently updated first.", "operationId": "list_projects", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectResponse" } } } } }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] }, "post": { "tags": [ "projects" ], "description": "Create an empty project (staged, not yet deployed). IDs must be DNS-safe and unused.", "operationId": "create_project", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateProjectRequest" } } }, "required": true }, "responses": { "201": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectResponse" } } } }, "400": { "description": "" }, "401": { "description": "" }, "409": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/projects/stats": { "get": { "tags": [ "stats" ], "description": "Stats, disk usage, and services for all projects in one call (dashboard polling endpoint).", "operationId": "all_project_stats", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchStatsResponse" } } } }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}": { "get": { "tags": [ "projects" ], "description": "Fetch a single project with its routes, services, and settings.", "operationId": "get_project", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectResponse" } } } }, "404": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] }, "delete": { "tags": [ "manage" ], "description": "Delete a project: containers and volumes on its node, project files, DB rows, and routes.", "operationId": "delete_project", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "404": { "description": "Project not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/deploy-logs": { "get": { "tags": [ "stats" ], "description": "Return the in-memory deploy log lines captured during the project's last deploy.", "operationId": "deploy_logs", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeployLogsResponse" } } } } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/disk-usage": { "get": { "tags": [ "stats" ], "description": "Total disk usage (GB) across a running project's containers.", "operationId": "project_disk_usage", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DiskUsageResponse" } } } }, "400": { "description": "Bad request" }, "404": { "description": "Project not found" }, "500": { "description": "Internal server error" }, "503": { "description": "Service unavailable" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/env": { "get": { "tags": [ "env" ], "description": "List runtime env var keys with masked previews; values are write-only and never returned.", "operationId": "get_project_env", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Env keys with masked previews (never plaintext values)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EnvResponse" } } } }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden (insufficient token scope / project binding)" }, "404": { "description": "Project not found" }, "503": { "description": "Node offline" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] }, "put": { "tags": [ "env" ], "description": "Set, delete, or replace runtime env vars; changes apply on the next container start or recreate.", "operationId": "update_project_env", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateEnvRequest" } } }, "required": true }, "responses": { "200": { "description": "Env updated; applies on next container start", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateEnvResponse" } } } }, "400": { "description": "Invalid keys/values" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden (manage scope required)" }, "404": { "description": "Project not found" }, "503": { "description": "Node offline" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] } }, "/projects/{id}/github/config": { "get": { "tags": [ "github" ], "description": "Read a project's GitHub link: repo, branch, path, workflow, and deploy token status.", "operationId": "get_config", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Link config", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectGithubConfigResponse" } } } }, "404": { "description": "No link for this project" } }, "security": [ { "session_auth": [] } ] }, "put": { "tags": [ "github" ], "description": "Create or replace a project's GitHub link (full config upsert).", "operationId": "put_config", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LinkRequest" } } }, "required": true }, "responses": { "200": { "description": "Linked — workflow + secrets written, token minted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectGithubConfigResponse" } } } }, "403": { "description": "Branch protected or missing workflow permissions" }, "404": { "description": "Project not found" }, "503": { "description": "GitHub not connected" } }, "security": [ { "session_auth": [] } ] }, "delete": { "tags": [ "github" ], "description": "Unlink a project from GitHub: revoke its deploy token and optionally clean the workflow file and secrets from the repo.", "operationId": "delete_config", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "remove_files", "in": "query", "description": "Also remove the workflow file + secrets from the repo (default true)", "required": false, "schema": { "type": "boolean" } } ], "responses": { "204": { "description": "Unlinked — token revoked, repo cleaned up best-effort" }, "404": { "description": "No link for this project" } }, "security": [ { "session_auth": [] } ] }, "patch": { "tags": [ "github" ], "description": "Update selected fields of a project's GitHub link.", "operationId": "patch_config", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PatchConfigRequest" } } }, "required": true }, "responses": { "200": { "description": "Toggled — workflow rewritten", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectGithubConfigResponse" } } } }, "404": { "description": "No link for this project" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/github/deploy": { "post": { "tags": [ "github" ], "description": "Dispatch the linked repo's deploy workflow now (build and push image, then deploy).", "operationId": "deploy_now", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeployRequest" } } }, "required": true }, "responses": { "202": { "description": "Workflow dispatched — watch the runs endpoint" }, "404": { "description": "No link for this project" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/github/rotate-token": { "post": { "tags": [ "github" ], "description": "Rotate the project's GitHub deploy token; the repo secret is overwritten before the old token is revoked.", "operationId": "rotate_token", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Token rotated — repo secret overwritten first" }, "404": { "description": "No link for this project" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/github/runs": { "get": { "tags": [ "github" ], "description": "Recent GitHub Actions workflow runs for the project's linked repo.", "operationId": "project_runs", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Recent runs of the linked workflow, newest first", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectRuns" } } } }, "404": { "description": "No link for this project" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/logs": { "get": { "tags": [ "stats" ], "description": "Tail stored log lines for a project; for multi-service projects, `service` selects a specific service (defaults to the public service).", "operationId": "project_logs", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "tail", "in": "query", "description": "Number of log lines to return", "required": false, "schema": { "type": "integer", "minimum": 0 } }, { "name": "service", "in": "query", "description": "Service name to filter logs", "required": false, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LogsResponse" } } } }, "404": { "description": "Project not found" }, "500": { "description": "Internal server error" }, "503": { "description": "Service unavailable" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/recreate": { "post": { "tags": [ "manage" ], "description": "Recreate containers without pulling images, picking up updated env files; optionally select specific services or pull latest images (redeploy).", "operationId": "recreate_project", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/RecreateRequest" } ] } } } }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "400": { "description": "Bad request" }, "404": { "description": "Project not found" }, "503": { "description": "Service unavailable" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/routes": { "get": { "tags": [ "projects" ], "description": "List custom routes (path-based and alias) for a project.", "operationId": "list_routes", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ProjectRouteResponse" } } } } }, "401": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] }, "post": { "tags": [ "projects" ], "description": "Create a custom route (path-based or subdomain alias); rejects aliases that clash with project IDs.", "operationId": "create_route", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateRouteRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProjectRouteResponse" } } } }, "400": { "description": "" }, "401": { "description": "" }, "409": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/routes/{route_id}": { "delete": { "tags": [ "projects" ], "description": "Delete a custom route and resync Caddy.", "operationId": "delete_route", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "route_id", "in": "path", "description": "Route ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "" }, "401": { "description": "" }, "404": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/services/{name}/restart": { "post": { "tags": [ "manage" ], "description": "Restart a single compose service (stop then start, recreate on the agent path).", "operationId": "restart_service", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "description": "Service name", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "404": { "description": "Not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/services/{name}/settings": { "patch": { "tags": [ "settings" ], "description": "Update per-service resource limits (memory, CPU) for a compose service.", "operationId": "update_service_settings", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "description": "Service name", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateServiceSettingsRequest" } } }, "required": true }, "responses": { "200": { "description": "" }, "401": { "description": "" }, "404": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/services/{name}/start": { "post": { "tags": [ "manage" ], "description": "Start a single compose service (recreates just that service).", "operationId": "start_service", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "description": "Service name", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "404": { "description": "Not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/services/{name}/stop": { "post": { "tags": [ "manage" ], "description": "Stop a single compose service; derives project status from remaining services.", "operationId": "stop_service", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "description": "Service name", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "404": { "description": "Not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/settings": { "patch": { "tags": [ "settings" ], "description": "Update project settings (name, custom domain, auto-stop/start, resource limits, app port); only provided fields change.", "operationId": "update_project_settings", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateSettingsRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } } }, "400": { "description": "" }, "404": { "description": "" }, "409": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/start": { "post": { "tags": [ "manage" ], "description": "Start all services for a project: fast-path docker start of existing containers, falling back to recreate. Handles local and remote nodes.", "operationId": "start_project", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "400": { "description": "Bad request" }, "404": { "description": "Project not found" }, "503": { "description": "Service unavailable" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/stats": { "get": { "tags": [ "stats" ], "description": "Per-service CPU, memory, and disk stats for one project (syncs status from Docker first).", "operationId": "project_stats", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatsResponse" } } } }, "404": { "description": "Project not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/stop": { "post": { "tags": [ "manage" ], "description": "Stop a project asynchronously (local Docker or via the node agent); Caddy routes are removed immediately.", "operationId": "stop_project", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } } }, "400": { "description": "Bad request" }, "404": { "description": "Project not found" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/volumes": { "delete": { "tags": [ "volumes" ], "description": "Remove all volumes for a project; returns the deleted volume names.", "operationId": "delete_all_volumes", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "All volumes deleted" }, "401": { "description": "Authentication required" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/projects/{id}/volumes/{name}": { "delete": { "tags": [ "volumes" ], "description": "Remove a specific named volume for a project.", "operationId": "delete_volume", "parameters": [ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": { "type": "string" } }, { "name": "name", "in": "path", "description": "Volume name", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "Volume deleted" }, "401": { "description": "Authentication required" }, "500": { "description": "Internal server error" } }, "security": [ { "session_auth": [] } ] } }, "/scan": { "get": { "tags": [ "scan" ], "description": "Find foreign (non-LiteBin) container groups on a node: local, all, or a specific node ID.", "operationId": "scan_containers", "parameters": [ { "name": "node_id", "in": "query", "required": false, "schema": { "type": [ "string", "null" ] } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScanResult" } } } }, "404": { "description": "Node not found" } }, "security": [ { "session_auth": [] } ] } }, "/scan/import": { "post": { "tags": [ "scan" ], "description": "Adopt scanned foreign container groups as LiteBin projects: write DB rows, resolve/rebuild compose, rewrite bind mounts, import into Docker, and optionally set up routing.", "operationId": "import_containers", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ImportResponse" } } } }, "400": { "description": "Bad request" }, "401": { "description": "Unauthorized" } }, "security": [ { "session_auth": [] } ] } }, "/settings": { "get": { "tags": [ "global-settings" ], "description": "Read global platform settings (domain, routing mode, Cloudflare, subdomains). Admin-only — contains secrets.", "operationId": "get_settings", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GlobalSettings" } } } }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] }, "patch": { "tags": [ "global-settings" ], "description": "Update global platform settings; domain changes must go through /settings/domain/apply.", "operationId": "update_settings", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UpdateGlobalSettings" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GlobalSettings" } } } }, "400": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/settings/cleanup-dns": { "post": { "tags": [ "global-settings" ], "description": "Delete all DNS records LiteBin created (per its ledger); user-created records are left alone.", "operationId": "cleanup_dns", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CleanupDnsResponse" } } } }, "400": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/settings/domain/apply": { "post": { "tags": [ "global-settings" ], "description": "Switch the platform domain as a background job (routes, agent Caddy, DNS cutover); poll the jobs endpoint.", "operationId": "domain_apply", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainApplyRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainApplyResponse" } } } }, "400": { "description": "" }, "409": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/settings/domain/jobs/{id}": { "get": { "tags": [ "global-settings" ], "description": "Domain-change job progress: per-step status, errors, and the resulting dashboard URL.", "operationId": "domain_job_status", "parameters": [ { "name": "id", "in": "path", "description": "Job ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainJob" } } } }, "404": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/settings/domain/jobs/{id}/retry": { "post": { "tags": [ "global-settings" ], "description": "Retry a failed domain-change job, resuming from the failed step.", "operationId": "domain_job_retry", "parameters": [ { "name": "id", "in": "path", "description": "Job ID", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainApplyResponse" } } } }, "400": { "description": "" }, "404": { "description": "" }, "409": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/settings/domain/preflight": { "post": { "tags": [ "global-settings" ], "description": "Check a candidate platform domain: DNS control, conflicts, TLS reachability; returns errors and warnings.", "operationId": "domain_preflight", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainPreflightRequest" } } }, "required": true }, "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DomainPreflightResponse" } } } }, "400": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/settings/sync-dns": { "post": { "tags": [ "global-settings" ], "description": "Reconcile Cloudflare DNS records for the platform domain and project custom domains.", "operationId": "sync_dns", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SyncDnsResponse" } } } }, "400": { "description": "" }, "500": { "description": "" } }, "security": [ { "session_auth": [] } ] } }, "/status": { "get": { "tags": [ "auth" ], "description": "Return system status: version, current user, nodes, and project count.", "operationId": "status", "responses": { "200": { "description": "System status", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StatusResponse" } } } }, "401": { "description": "Unauthorized" } }, "security": [ { "session_auth": [] } ] } }, "/system/stats": { "get": { "tags": [ "health" ], "description": "Memory, CPU, and disk usage of the LiteBin stack containers (orchestrator, dashboard, caddy).", "operationId": "system_stats", "responses": { "200": { "description": "System stats response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SystemStatsResponse" } } } } }, "security": [ { "session_auth": [] } ] } }, "/whoami": { "get": { "tags": [ "health" ], "description": "Identify the credential making this request: session username or token identity (id, name, scope, project binding).", "operationId": "whoami", "responses": { "200": { "description": "Credential identity", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WhoamiResponse" } } } }, "401": { "description": "Unauthorized" } }, "security": [ { "session_auth": [] }, { "bearer_token": [] } ] } } }, "components": { "schemas": { "AppCredentialsRequest": { "type": "object", "description": "Reattach to an existing GitHub App (manifest flow always mints a new one).", "required": [ "app_id", "private_key_pem" ], "properties": { "app_id": { "type": "integer", "format": "int64" }, "private_key_pem": { "type": "string", "description": "Full contents of the app's .pem private key file." }, "webhook_secret": { "type": [ "string", "null" ], "description": "Only needed when the app has a webhook configured." } } }, "BackupActionResponse": { "type": "object", "required": [ "status" ], "properties": { "restarting": { "type": "boolean", "description": "Set when the orchestrator is about to restart (restore applied)." }, "status": { "$ref": "#/components/schemas/BackupStatus" } } }, "BackupPoint": { "type": "object", "description": "A user-named restore point — a `VACUUM INTO` snapshot of the live database,\nkept as a local file so it survives replica switches and disabled backup.", "required": [ "id", "name", "timestamp", "size_bytes", "available" ], "properties": { "available": { "type": "boolean", "description": "Snapshot file present. Rows from before the file-based points have none." }, "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "size_bytes": { "type": "integer", "format": "int64", "minimum": 0 }, "timestamp": { "type": "string", "description": "When the point was taken (RFC 3339)." } } }, "BackupPointRequest": { "type": "object", "required": [ "name" ], "properties": { "name": { "type": "string", "description": "Label for the current moment (1-64 chars)." } } }, "BackupRestoreLocalRequest": { "type": "object", "required": [ "date" ], "properties": { "date": { "type": "string", "description": "Daily snapshot date (YYYY-MM-DD)." } } }, "BackupRestorePointRequest": { "type": "object", "required": [ "point_id" ], "properties": { "point_id": { "type": "integer", "format": "int64" } } }, "BackupRestoreRequest": { "type": "object", "properties": { "timestamp": { "type": [ "string", "null" ], "description": "Point-in-time restore (RFC 3339). Latest snapshot when omitted." } } }, "BackupS3Request": { "type": "object", "required": [ "endpoint", "bucket", "access_key_id", "secret_access_key" ], "properties": { "access_key_id": { "type": "string" }, "bucket": { "type": "string" }, "confirm_existing": { "type": "boolean", "description": "Proceed even though the destination prefix already holds backup files\n(LiteBin starts a new backup alongside them; old files are left alone)." }, "endpoint": { "type": "string" }, "prefix": { "type": [ "string", "null" ] }, "secret_access_key": { "type": "string" } } }, "BackupS3Response": { "type": "object", "description": "Response for `POST /backup/s3` — `existing_files` is set when the destination\nprefix already holds files and nothing was saved (re-submit to proceed).", "required": [ "status" ], "properties": { "existing_files": { "type": "boolean" }, "status": { "$ref": "#/components/schemas/BackupStatus" } } }, "BackupS3Status": { "type": "object", "required": [ "configured" ], "properties": { "bucket": { "type": [ "string", "null" ] }, "configured": { "type": "boolean" }, "endpoint": { "type": [ "string", "null" ] }, "prefix": { "type": [ "string", "null" ] } } }, "BackupStatus": { "type": "object", "required": [ "enabled", "available", "sidecar_installed", "sidecar_running", "dailies", "points", "s3" ], "properties": { "available": { "type": "boolean", "description": "Orchestrator data storage can be shared with a sidecar (running in a container)." }, "coverage_start": { "type": [ "string", "null" ], "description": "Earliest restorable moment (RFC 3339) across the active replica." }, "dailies": { "type": "array", "items": { "$ref": "#/components/schemas/DailySnapshot" }, "description": "Local daily snapshots (s3/r2 mode), newest first." }, "db_size_bytes": { "type": [ "integer", "null" ], "format": "int64", "description": "Live database size in bytes.", "minimum": 0 }, "destination_health": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DestinationHealth", "description": "Last s3/r2 destination health probe (None before the first check)." } ] }, "enabled": { "type": "boolean", "description": "Backup turned on (sidecar should be running)." }, "last_sync": { "type": [ "string", "null" ], "description": "Last local replica write time (RFC 3339), None before the first sync." }, "points": { "type": "array", "items": { "$ref": "#/components/schemas/BackupPoint" }, "description": "User-named restore points, newest first." }, "replica": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ReplicaInfo", "description": "Active replica store summary (local file store, or the s3/r2 staging store)." } ] }, "revertible_to": { "type": [ "string", "null" ], "description": "When the kept pre-restore database was last live (RFC 3339) — the undo\ntarget. None when no restore has been applied." }, "s3": { "$ref": "#/components/schemas/BackupS3Status" }, "sidecar_installed": { "type": "boolean" }, "sidecar_running": { "type": "boolean" } } }, "BatchStatsResponse": { "type": "object", "required": [ "stats" ], "properties": { "stats": { "type": "array", "items": { "$ref": "#/components/schemas/StatsResponse" } } } }, "BranchSummary": { "type": "object", "required": [ "name", "protected" ], "properties": { "name": { "type": "string" }, "protected": { "type": "boolean" } } }, "BulkRunStatus": { "type": "object", "required": [ "project_id", "repo_full_name" ], "properties": { "conclusion": { "type": [ "string", "null" ] }, "project_id": { "type": "string" }, "repo_full_name": { "type": "string" }, "run_at": { "type": [ "integer", "null" ], "format": "int64" }, "run_url": { "type": [ "string", "null" ] }, "status": { "type": [ "string", "null" ] } } }, "BulkStatus": { "type": "object", "required": [ "projects" ], "properties": { "projects": { "type": "array", "items": { "$ref": "#/components/schemas/BulkRunStatus" }, "description": "Latest run per linked project (cached snapshot when GitHub was unreachable)." } } }, "ChangePasswordRequest": { "type": "object", "required": [ "current_password", "new_password" ], "properties": { "current_password": { "type": "string" }, "new_password": { "type": "string" } } }, "ChangePasswordResponse": { "type": "object", "required": [ "success" ], "properties": { "success": { "type": "boolean" } } }, "CleanupDnsResponse": { "type": "object", "required": [ "deleted_count" ], "properties": { "deleted_count": { "type": "integer", "minimum": 0 } } }, "ConflictResponse": { "type": "object", "required": [ "error", "project_ids" ], "properties": { "error": { "type": "string" }, "project_ids": { "type": "array", "items": { "type": "string" } } } }, "ConnectionStatus": { "type": "object", "required": [ "connected" ], "properties": { "connected": { "type": "boolean" }, "login": { "type": [ "string", "null" ] }, "mode": { "type": [ "string", "null" ] }, "status": { "type": [ "string", "null" ] }, "status_message": { "type": [ "string", "null" ] } } }, "ConnectionSummary": { "type": "object", "required": [ "id", "mode", "status", "installations" ], "properties": { "id": { "type": "integer", "format": "int64" }, "installations": { "type": "array", "items": { "$ref": "#/components/schemas/InstallationSummary" } }, "label": { "type": [ "string", "null" ], "description": "App slug or PAT login." }, "mode": { "type": "string" }, "status": { "type": "string" }, "status_message": { "type": [ "string", "null" ] } } }, "ContainerStatus": { "type": "object", "description": "Agent container status response", "required": [ "state", "cpu_percent", "memory_usage", "memory_limit" ], "properties": { "cpu_percent": { "type": "number", "format": "double" }, "mapped_port": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "memory_limit": { "type": "integer", "format": "int64", "minimum": 0 }, "memory_usage": { "type": "integer", "format": "int64", "minimum": 0 }, "state": { "type": "string" } } }, "CreateNodeRequest": { "type": "object", "required": [ "name", "host" ], "properties": { "agent_port": { "type": [ "integer", "null" ], "format": "int64" }, "host": { "type": "string" }, "name": { "type": "string" }, "public_ip": { "type": [ "string", "null" ] }, "region": { "type": [ "string", "null" ] } } }, "CreateProjectRequest": { "type": "object", "required": [ "id" ], "properties": { "description": { "type": [ "string", "null" ] }, "id": { "type": "string" }, "is_background": { "type": "boolean" }, "name": { "type": [ "string", "null" ] } } }, "CreateRouteRequest": { "type": "object", "required": [ "route_type", "upstream" ], "properties": { "path": { "type": [ "string", "null" ] }, "priority": { "type": [ "integer", "null" ], "format": "int64" }, "route_type": { "type": "string" }, "subdomain": { "type": [ "string", "null" ] }, "upstream": { "type": "string" } } }, "CreateTokenRequest": { "type": "object", "properties": { "expires_at": { "type": [ "integer", "null" ], "format": "int64" }, "name": { "type": [ "string", "null" ] }, "project_id": { "type": [ "string", "null" ] }, "scope": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TokenScope", "description": "Cumulative access level: read, deploy (default), manage, or admin." } ] } } }, "CreateTokenResponse": { "type": "object", "required": [ "token", "token_info" ], "properties": { "token": { "type": "string" }, "token_info": { "$ref": "#/components/schemas/DeployTokenResponse" } } }, "DailySnapshot": { "type": "object", "description": "A dated local snapshot restored from the s3/r2 replica.", "required": [ "date", "timestamp", "size_bytes" ], "properties": { "date": { "type": "string", "description": "File-name date, UTC (YYYY-MM-DD)." }, "size_bytes": { "type": "integer", "format": "int64", "minimum": 0 }, "timestamp": { "type": "string", "description": "When the snapshot was taken (RFC 3339, file mtime)." } } }, "DeployLogsResponse": { "type": "object", "required": [ "project_id", "lines" ], "properties": { "lines": { "type": "array", "items": { "type": "string" }, "description": "In-memory deploy log lines from the project's last deploy." }, "project_id": { "type": "string" } } }, "DeployRequest": { "type": "object", "properties": { "ref": { "type": [ "string", "null" ], "description": "Branch/ref to deploy; defaults to the linked branch." } } }, "DeployResponse": { "type": "object", "required": [ "status", "project_id", "message" ], "properties": { "message": { "type": "string" }, "project_id": { "type": "string" }, "status": { "type": "string" }, "url": { "type": [ "string", "null" ], "description": "Managed application URL. Background projects return `null`." } } }, "DeployTokenResponse": { "type": "object", "required": [ "id", "scope", "created_at" ], "properties": { "created_at": { "type": "integer", "format": "int64" }, "expires_at": { "type": [ "integer", "null" ], "format": "int64" }, "id": { "type": "string" }, "last_used_at": { "type": [ "integer", "null" ], "format": "int64" }, "name": { "type": [ "string", "null" ] }, "project_id": { "type": [ "string", "null" ] }, "scope": { "$ref": "#/components/schemas/TokenScope" } } }, "DeployType": { "type": "string", "enum": [ "image", "compose" ] }, "DestinationHealth": { "type": "object", "description": "Last destination health probe (kept in memory; None before the first check).", "required": [ "ok", "checked_at" ], "properties": { "checked_at": { "type": "string", "description": "Last probe time (RFC 3339) — internal, feeds the health dot." }, "detail": { "type": [ "string", "null" ], "description": "Failure detail (auth error, unreachable, sidecar down)." }, "failing_since": { "type": [ "string", "null" ], "description": "First persistent failure (RFC 3339, from sidecar error logs) — the\n\"failing since\" shown to users. None while healthy." }, "ok": { "type": "boolean" } } }, "DeviceApproveRequest": { "type": "object", "required": [ "user_code", "approve", "scope" ], "properties": { "approve": { "type": "boolean" }, "project_id": { "type": [ "string", "null" ], "description": "Optional: bind the token to a single project." }, "scope": { "$ref": "#/components/schemas/TokenScope" }, "user_code": { "type": "string" } } }, "DeviceRequestInfo": { "type": "object", "required": [ "user_code", "suggested_scope", "expires_at" ], "properties": { "client_name": { "type": [ "string", "null" ] }, "expires_at": { "type": "integer", "format": "int64" }, "suggested_scope": { "$ref": "#/components/schemas/TokenScope" }, "user_code": { "type": "string" } } }, "DeviceStartRequest": { "type": "object", "properties": { "client_name": { "type": [ "string", "null" ], "description": "Shown on the approve page, e.g. \"l8b CLI on my-laptop\"." }, "scope": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TokenScope", "description": "Scope the client asks for; the approver makes the final call." } ] } } }, "DeviceStartResponse": { "type": "object", "required": [ "device_code", "user_code", "verification_url", "expires_in", "interval" ], "properties": { "device_code": { "type": "string", "description": "Opaque claim secret — only the client that started the flow can poll with it." }, "expires_in": { "type": "integer", "format": "int64" }, "interval": { "type": "integer", "format": "int64", "minimum": 0 }, "user_code": { "type": "string", "description": "Human-typed verifier, e.g. `L8B-4KX2QW`." }, "verification_url": { "type": "string" } } }, "DeviceTokenRequest": { "type": "object", "required": [ "device_code" ], "properties": { "device_code": { "type": "string" } } }, "DeviceTokenResponse": { "type": "object", "required": [ "status" ], "properties": { "project_id": { "type": [ "string", "null" ] }, "scope": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TokenScope" } ] }, "status": { "type": "string", "description": "pending | ok | denied | expired" }, "token": { "type": [ "string", "null" ] } } }, "DiskUsageResponse": { "type": "object", "required": [ "project_id", "size_gb" ], "properties": { "project_id": { "type": "string" }, "size_gb": { "type": "number", "format": "double" } } }, "DomainApplyRequest": { "type": "object", "required": [ "domain" ], "properties": { "acknowledge_dns": { "type": "boolean" }, "domain": { "type": "string" } } }, "DomainApplyResponse": { "type": "object", "required": [ "job_id" ], "properties": { "job_id": { "type": "string" } } }, "DomainJob": { "type": "object", "required": [ "id", "status", "domain", "old_domain", "steps", "resume_from", "dashboard_url" ], "properties": { "dashboard_url": { "type": "string" }, "domain": { "type": "string" }, "error": { "type": [ "string", "null" ] }, "id": { "type": "string" }, "old_domain": { "type": "string" }, "resume_from": { "type": "integer", "description": "Index of the step to resume from on retry.", "minimum": 0 }, "status": { "$ref": "#/components/schemas/DomainJobStatus" }, "steps": { "type": "array", "items": { "$ref": "#/components/schemas/DomainJobStep" } } } }, "DomainJobStatus": { "type": "string", "description": "Status of a platform domain-change job.", "enum": [ "pending", "running", "failed", "completed" ] }, "DomainJobStep": { "type": "object", "required": [ "id", "label", "status" ], "properties": { "error": { "type": [ "string", "null" ] }, "id": { "type": "string" }, "label": { "type": "string" }, "status": { "$ref": "#/components/schemas/DomainStepStatus" } } }, "DomainPreflightRequest": { "type": "object", "required": [ "domain" ], "properties": { "domain": { "type": "string" } } }, "DomainPreflightResponse": { "type": "object", "required": [ "ok", "domain", "errors", "warnings" ], "properties": { "domain": { "type": "string" }, "errors": { "type": "array", "items": { "type": "string" } }, "ok": { "type": "boolean" }, "warnings": { "type": "array", "items": { "type": "string" } } } }, "DomainStepStatus": { "type": "string", "enum": [ "pending", "running", "done", "failed", "skipped" ] }, "EnvResponse": { "type": "object", "required": [ "project_id", "vars", "pending_apply" ], "properties": { "pending_apply": { "type": "boolean", "description": "`.env` differs from what the container was last started with." }, "project_id": { "type": "string" }, "vars": { "type": "array", "items": { "$ref": "#/components/schemas/EnvVarInfo" } } } }, "EnvUpdateMode": { "type": "string", "enum": [ "merge", "replace" ] }, "EnvVarInfo": { "type": "object", "required": [ "key", "masked" ], "properties": { "key": { "type": "string" }, "masked": { "type": "string", "description": "Constant mask when set; empty string for empty values." } } }, "ErrorResponse": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string" } } }, "GlobalSettings": { "type": "object", "required": [ "default_memory_limit_mb", "default_cpu_limit", "projects_dir", "domain", "dns_target", "routing_mode", "cloudflare_api_token", "cloudflare_zone_id", "dashboard_subdomain", "poke_subdomain", "tryout" ], "properties": { "cloudflare_api_token": { "type": "string" }, "cloudflare_zone_id": { "type": "string" }, "dashboard_subdomain": { "type": "string" }, "default_cpu_limit": { "type": "number", "format": "double" }, "default_memory_limit_mb": { "type": "integer", "format": "int64" }, "dns_target": { "type": "string" }, "domain": { "type": "string" }, "poke_subdomain": { "type": "string" }, "projects_dir": { "type": "string" }, "routing_mode": { "type": "string" }, "tryout": { "type": "boolean", "description": "True when platform domain is sslip.io / nip.io tryout DNS." } } }, "HealthReport": { "type": "object", "description": "Agent health response", "required": [ "version", "protocol_version", "architecture", "memory_available", "memory_total", "cpu_cores", "disk_free", "disk_total", "container_count", "image_stats" ], "properties": { "architecture": { "type": "string" }, "container_count": { "type": "integer", "format": "int32", "minimum": 0 }, "cpu_cores": { "type": "integer", "format": "int32", "minimum": 0 }, "disk_free": { "type": "integer", "format": "int64", "minimum": 0 }, "disk_total": { "type": "integer", "format": "int64", "minimum": 0 }, "docker_rootless": { "type": [ "boolean", "null" ] }, "image_stats": { "$ref": "#/components/schemas/ImageStats" }, "memory_available": { "type": "integer", "format": "int64", "minimum": 0 }, "memory_total": { "type": "integer", "format": "int64", "minimum": 0 }, "protocol_version": { "type": "integer", "format": "int32", "description": "Agent API protocol version used for compatibility checks.", "minimum": 0 }, "public_ip": { "type": [ "string", "null" ] }, "version": { "type": "string" } } }, "HealthResponse": { "type": "object", "required": [ "status" ], "properties": { "status": { "type": "string" } } }, "ImageStats": { "type": "object", "description": "Docker image statistics for a node", "required": [ "dangling_count", "dangling_size", "in_use_count", "in_use_size", "total_count", "total_size" ], "properties": { "dangling_count": { "type": "integer", "format": "int64", "minimum": 0 }, "dangling_size": { "type": "integer", "format": "int64", "minimum": 0 }, "in_use_count": { "type": "integer", "format": "int64", "minimum": 0 }, "in_use_size": { "type": "integer", "format": "int64", "minimum": 0 }, "total_count": { "type": "integer", "format": "int64", "minimum": 0 }, "total_size": { "type": "integer", "format": "int64", "minimum": 0 } } }, "ImportGroupRequest": { "type": "object", "required": [ "node_id", "project_id", "group_key", "setup_routing", "containers", "deploy_type", "compose_file_found", "env_file_found" ], "properties": { "compose_file_found": { "type": "boolean" }, "compose_working_dir": { "type": [ "string", "null" ] }, "containers": { "type": "array", "items": { "$ref": "#/components/schemas/ScanContainer" }, "description": "Full container data echoed back from the scan response." }, "deploy_type": { "$ref": "#/components/schemas/DeployType" }, "description": { "type": [ "string", "null" ] }, "env_file_found": { "type": "boolean" }, "group_key": { "type": "string" }, "name": { "type": [ "string", "null" ] }, "node_id": { "type": "string" }, "project_id": { "type": "string" }, "public_service": { "type": [ "string", "null" ] }, "setup_routing": { "type": "boolean" } } }, "ImportRequest": { "type": "object", "required": [ "groups" ], "properties": { "groups": { "type": "array", "items": { "$ref": "#/components/schemas/ImportGroupRequest" } } } }, "ImportResponse": { "type": "object", "required": [ "imported", "errors" ], "properties": { "errors": { "type": "array", "items": { "type": "string" } }, "imported": { "type": "array", "items": { "$ref": "#/components/schemas/ImportedGroup" } } } }, "ImportedGroup": { "type": "object", "required": [ "project_id", "node_id", "containers_imported", "warnings" ], "properties": { "containers_imported": { "type": "array", "items": { "type": "string" } }, "node_id": { "type": "string" }, "project_id": { "type": "string" }, "warnings": { "type": "array", "items": { "type": "string" } } } }, "InstallationSummary": { "type": "object", "required": [ "id", "connection_id", "account_login", "account_type", "repository_selection", "status" ], "properties": { "account_avatar": { "type": [ "string", "null" ] }, "account_login": { "type": "string" }, "account_type": { "type": "string" }, "connection_id": { "type": "integer", "format": "int64", "description": "Owning app connection." }, "id": { "type": "integer", "format": "int64" }, "repository_selection": { "type": "string" }, "status": { "type": "string" } } }, "LinkRequest": { "type": "object", "required": [ "repo_full_name" ], "properties": { "auto_deploy": { "type": [ "boolean", "null" ] }, "branch": { "type": [ "string", "null" ], "description": "Empty/omitted = repo default branch." }, "cmd": { "type": [ "string", "null" ] }, "commit_message": { "type": [ "string", "null" ], "description": "Commit message for the workflow file; defaults when empty." }, "compose_file": { "type": [ "string", "null" ] }, "compose_services": { "type": [ "array", "null" ], "items": { "type": "string" } }, "connection_id": { "type": [ "integer", "null" ], "format": "int64", "description": "PAT mode: connection serving this repo." }, "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "deploy_kind": { "type": [ "string", "null" ], "description": "\"single\" (default) | \"compose\"." }, "dockerfile": { "type": [ "string", "null" ] }, "installation_id": { "type": [ "integer", "null" ], "format": "int64", "description": "App mode: installation the repo lives under (PAT mode: none)." }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "node_id": { "type": [ "string", "null" ] }, "path": { "type": [ "string", "null" ], "description": "Subdirectory of the repo the app lives in (empty = repo root)." }, "port": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 }, "repo_full_name": { "type": "string" }, "repo_id": { "type": [ "integer", "null" ], "format": "int64" } } }, "ListTokensQuery": { "type": "object", "properties": { "project_id": { "type": [ "string", "null" ] } } }, "LoginRequest": { "type": "object", "required": [ "username", "password" ], "properties": { "password": { "type": "string" }, "username": { "type": "string" } } }, "LoginResponse": { "type": "object", "required": [ "user" ], "properties": { "user": { "$ref": "#/components/schemas/UserResponse" } } }, "LogsQuery": { "type": "object", "properties": { "service": { "type": [ "string", "null" ] }, "tail": { "type": [ "integer", "null" ], "minimum": 0 } } }, "LogsResponse": { "type": "object", "required": [ "project_id", "lines" ], "properties": { "lines": { "type": "array", "items": { "type": "string" } }, "project_id": { "type": "string" }, "service_name": { "type": [ "string", "null" ] } } }, "ManifestExchangeRequest": { "type": "object", "required": [ "code" ], "properties": { "code": { "type": "string", "description": "Single-use code GitHub appended to the redirect_url after registration." } } }, "ManifestExchangeResponse": { "type": "object", "required": [ "app_slug", "install_url" ], "properties": { "app_slug": { "type": "string" }, "install_url": { "type": "string", "description": "Where the user picks which repos the app may see." } } }, "ManifestStartRequest": { "type": "object", "description": "Guided-flow input: name + optional webhook override.", "properties": { "name": { "type": [ "string", "null" ], "description": "GitHub App display name. Default \"l8bin\". Max 34 chars, letters,\ndigits, spaces and hyphens (GitHub's rules)." }, "webhook_url": { "type": [ "string", "null" ], "description": "Webhook URL override; omit to use {dashboard}/github/webhook (or no\nwebhook at all when the dashboard isn't publicly reachable)." } } }, "ManifestStartResponse": { "type": "object", "required": [ "manifest" ], "properties": { "manifest": { "type": "string", "description": "Raw manifest JSON — the dashboard POSTs it to\ngithub.com/settings/apps/new as the `manifest` form field (GitHub's\ndocumented prefill flow; no URL encoding involved)." } } }, "MessageResponse": { "type": "object", "required": [ "message" ], "properties": { "message": { "type": "string" }, "warnings": { "type": "array", "items": { "type": "string" } } } }, "MetaResponse": { "type": "object", "required": [ "domain", "dashboard_subdomain", "poke_subdomain", "routing_mode", "version" ], "properties": { "dashboard_subdomain": { "type": "string" }, "domain": { "type": "string", "description": "Platform domain used for project URLs: `https://{project_id}.{domain}`." }, "poke_subdomain": { "type": "string" }, "routing_mode": { "type": "string", "description": "`master_proxy` or `cloudflare_dns`." }, "version": { "type": "string", "description": "Orchestrator version." } } }, "Node": { "type": "object", "required": [ "id", "name", "host", "agent_port", "status", "container_count", "fail_count", "created_at", "updated_at" ], "properties": { "agent_port": { "type": "integer", "format": "int64" }, "architecture": { "type": [ "string", "null" ] }, "available_memory": { "type": [ "integer", "null" ], "format": "int64" }, "container_count": { "type": "integer", "format": "int64" }, "created_at": { "type": "integer", "format": "int64" }, "disk_free": { "type": [ "integer", "null" ], "format": "int64" }, "disk_total": { "type": [ "integer", "null" ], "format": "int64" }, "fail_count": { "type": "integer", "format": "int64" }, "host": { "type": "string" }, "id": { "type": "string" }, "last_seen_at": { "type": [ "integer", "null" ], "format": "int64" }, "name": { "type": "string" }, "public_ip": { "type": [ "string", "null" ] }, "region": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/NodeStatus" }, "total_cpu": { "type": [ "number", "null" ], "format": "double" }, "total_memory": { "type": [ "integer", "null" ], "format": "int64" }, "updated_at": { "type": "integer", "format": "int64" }, "version": { "type": [ "string", "null" ] } } }, "NodeImageStatsResponse": { "type": "object", "required": [ "node_id", "node_name", "image_stats" ], "properties": { "image_stats": { "$ref": "#/components/schemas/ImageStats" }, "node_id": { "type": "string" }, "node_name": { "type": "string" } } }, "NodeResponse": { "allOf": [ { "$ref": "#/components/schemas/Node" }, { "type": "object", "required": [ "recommended" ], "properties": { "recommended": { "type": "boolean" } } } ] }, "NodeStatus": { "type": "string", "enum": [ "online", "offline", "pending_setup", "decommissioned" ] }, "PatConnected": { "type": "object", "required": [ "login", "warnings" ], "properties": { "login": { "type": "string" }, "warnings": { "type": "array", "items": { "type": "string" }, "description": "Non-fatal notes (e.g. missing classic scopes on a fine-grained PAT)." } } }, "PatRequest": { "type": "object", "required": [ "token" ], "properties": { "token": { "type": "string" } } }, "PatchConfigRequest": { "type": "object", "required": [ "auto_deploy" ], "properties": { "auto_deploy": { "type": "boolean" } } }, "PortMismatch": { "type": "object", "description": "The port the proxy dials vs the ports the container declares.", "required": [ "routed_port", "container_ports" ], "properties": { "container_ports": { "type": "array", "items": { "type": "integer", "format": "int32", "minimum": 0 } }, "routed_port": { "type": "integer", "format": "int32", "minimum": 0 } } }, "Project": { "type": "object", "required": [ "id", "user_id", "is_background", "status", "auto_stop_enabled", "auto_stop_timeout_mins", "auto_start_enabled", "allow_raw_ports", "allow_docker_access", "created_at", "updated_at" ], "properties": { "allow_docker_access": { "type": "boolean" }, "allow_raw_ports": { "type": "boolean" }, "auto_start_enabled": { "type": "boolean" }, "auto_stop_enabled": { "type": "boolean" }, "auto_stop_timeout_mins": { "type": "integer", "format": "int64" }, "cmd": { "type": [ "string", "null" ] }, "container_id": { "type": [ "string", "null" ] }, "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "created_at": { "type": "integer", "format": "int64" }, "custom_domain": { "type": [ "string", "null" ] }, "deploy_type": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/DeployType" } ] }, "description": { "type": [ "string", "null" ] }, "id": { "type": "string" }, "image": { "type": [ "string", "null" ] }, "internal_port": { "type": [ "integer", "null" ], "format": "int64" }, "is_background": { "type": "boolean" }, "last_active_at": { "type": [ "integer", "null" ], "format": "int64" }, "mapped_port": { "type": [ "integer", "null" ], "format": "int64" }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "name": { "type": [ "string", "null" ] }, "node_id": { "type": [ "string", "null" ] }, "service_count": { "type": [ "integer", "null" ], "format": "int64" }, "service_summary": { "type": [ "string", "null" ] }, "status": { "$ref": "#/components/schemas/ProjectStatus" }, "updated_at": { "type": "integer", "format": "int64" }, "user_id": { "type": "string" }, "volumes": { "type": [ "string", "null" ] } } }, "ProjectGithubConfigResponse": { "type": "object", "required": [ "project_id", "repo_full_name", "branch", "workflow_path", "deploy_kind", "auto_deploy", "inputs" ], "properties": { "auto_deploy": { "type": "boolean" }, "branch": { "type": "string" }, "compose_file": { "type": [ "string", "null" ] }, "deploy_kind": { "type": "string" }, "inputs": { "$ref": "#/components/schemas/WorkflowInputs" }, "project_id": { "type": "string" }, "repo_full_name": { "type": "string" }, "workflow_path": { "type": "string" } } }, "ProjectResponse": { "type": "object", "description": "Project response for the API — project metadata + public_stats.\nThe internal `Project` struct (with all DB columns) is used by backend code;\nthis struct is the API-facing shape.", "required": [ "id", "user_id", "is_background", "is_staged", "status", "auto_stop_enabled", "auto_stop_timeout_mins", "auto_start_enabled", "allow_raw_ports", "created_at", "updated_at" ], "properties": { "allow_raw_ports": { "type": "boolean" }, "auto_start_enabled": { "type": "boolean" }, "auto_stop_enabled": { "type": "boolean" }, "auto_stop_timeout_mins": { "type": "integer", "format": "int64" }, "created_at": { "type": "integer", "format": "int64" }, "custom_domain": { "type": [ "string", "null" ] }, "deploy_type": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "id": { "type": "string" }, "is_background": { "type": "boolean" }, "is_staged": { "type": "boolean", "description": "True when deployment artifacts are ready for a staged project to start." }, "last_active_at": { "type": [ "integer", "null" ], "format": "int64" }, "name": { "type": [ "string", "null" ] }, "node_id": { "type": [ "string", "null" ] }, "port_mismatch": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/PortMismatch", "description": "Waker wrong-port hint; absent when no 502 signal (heuristic, not health)." } ] }, "public_stats": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/ServiceInfo" } ] }, "service_count": { "type": [ "integer", "null" ], "format": "int64" }, "service_summary": { "type": [ "string", "null" ] }, "status": { "type": "string" }, "updated_at": { "type": "integer", "format": "int64" }, "user_id": { "type": "string" } } }, "ProjectRouteResponse": { "type": "object", "required": [ "id", "project_id", "route_type", "upstream", "priority", "created_at" ], "properties": { "created_at": { "type": "integer", "format": "int64" }, "id": { "type": "string" }, "path": { "type": [ "string", "null" ] }, "priority": { "type": "integer", "format": "int64" }, "project_id": { "type": "string" }, "route_type": { "type": "string" }, "subdomain": { "type": [ "string", "null" ] }, "upstream": { "type": "string" } } }, "ProjectRuns": { "type": "object", "required": [ "project_id", "repo_full_name", "runs" ], "properties": { "project_id": { "type": "string" }, "repo_full_name": { "type": "string" }, "runs": { "type": "array", "items": { "$ref": "#/components/schemas/WorkflowRun" } } } }, "ProjectService": { "type": "object", "description": "A service within a project (one row per service in `project_services`).", "required": [ "project_id", "service_name", "image", "is_public", "status", "is_oneshot" ], "properties": { "cmd": { "type": [ "string", "null" ] }, "container_id": { "type": [ "string", "null" ] }, "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "depends_on": { "type": [ "string", "null" ] }, "image": { "type": "string" }, "instance_id": { "type": [ "string", "null" ] }, "is_oneshot": { "type": "boolean" }, "is_public": { "type": "boolean" }, "mapped_port": { "type": [ "integer", "null" ], "format": "int64" }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "port": { "type": [ "integer", "null" ], "format": "int64" }, "project_id": { "type": "string" }, "service_name": { "type": "string" }, "status": { "$ref": "#/components/schemas/ProjectStatus" } } }, "ProjectStatus": { "type": "string", "enum": [ "pending", "stopped", "running", "deploying", "importing", "stopping", "error", "degraded", "unconfigured", "completed" ] }, "ProjectVolume": { "type": "object", "description": "A volume definition for a service (one row per mount in `project_volumes`).", "required": [ "project_id", "service_name", "container_path" ], "properties": { "container_path": { "type": "string" }, "project_id": { "type": "string" }, "service_name": { "type": "string" }, "volume_name": { "type": [ "string", "null" ] } } }, "RecreateRequest": { "type": "object", "description": "POST /projects/:id/recreate\nRemove and recreate the container without pulling the image.\nPicks up updated env files from the agent's project directory.\nFor multi-service: accepts optional `services` array in JSON body for selective recreate.\nSet `pull_images: true` to pull latest images before recreating (redeploy).", "properties": { "pull_images": { "type": [ "boolean", "null" ] }, "services": { "type": [ "array", "null" ], "items": { "type": "string" } } } }, "RegisterRequest": { "type": "object", "required": [ "username", "password" ], "properties": { "email": { "type": [ "string", "null" ] }, "password": { "type": "string" }, "username": { "type": "string" } } }, "ReplicaInfo": { "type": "object", "description": "Snapshot of the local LTX store. Litestream ≥0.5 keeps one continuous store\nat `/ltx//-.ltx` — no generation dirs — so\nrestore coverage is a window: [coverage_start, now].", "required": [ "file_count", "size_bytes" ], "properties": { "coverage_start": { "type": [ "string", "null" ], "description": "Earliest restorable moment (RFC 3339): the later of store creation and\nthe retention horizon." }, "file_count": { "type": "integer", "format": "int64", "minimum": 0 }, "last_write": { "type": [ "string", "null" ], "description": "Newest LTX write (RFC 3339) — None before the first sync." }, "size_bytes": { "type": "integer", "format": "int64", "minimum": 0 } } }, "RepoDetail": { "type": "object", "required": [ "id", "full_name", "private", "default_branch", "html_url", "kind", "dockerfile_present" ], "properties": { "compose_file": { "type": [ "string", "null" ] }, "default_branch": { "type": "string" }, "detected_port": { "type": [ "integer", "null" ], "format": "int32", "description": "First `EXPOSE` port of the detected Dockerfile — link-form prefill.", "minimum": 0 }, "dockerfile_present": { "type": "boolean" }, "full_name": { "type": "string" }, "html_url": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "kind": { "type": "string", "description": "What the deploy form should default to." }, "private": { "type": "boolean" } } }, "RepoListQuery": { "type": "object", "properties": { "connection_id": { "type": [ "integer", "null" ], "format": "int64", "description": "PAT mode: list this connection's repos (user repos the token sees)." }, "installation_id": { "type": [ "integer", "null" ], "format": "int64", "description": "App mode: list this installation's repos." }, "page": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } }, "RepoPage": { "type": "object", "required": [ "repos", "page" ], "properties": { "page": { "type": "integer", "format": "int32", "minimum": 0 }, "repos": { "type": "array", "items": { "$ref": "#/components/schemas/RepoSummary" } }, "total_count": { "type": [ "integer", "null" ], "format": "int64" } } }, "RepoSummary": { "type": "object", "description": "Dashboard-facing repo list item.", "required": [ "id", "full_name", "private", "default_branch", "html_url", "owner_login" ], "properties": { "default_branch": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "full_name": { "type": "string" }, "html_url": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "owner_avatar": { "type": [ "string", "null" ] }, "owner_login": { "type": "string" }, "private": { "type": "boolean" }, "pushed_at": { "type": [ "string", "null" ] } } }, "RoutingMode": { "type": "string", "enum": [ "master_proxy", "cloudflare_dns" ] }, "ScanContainer": { "type": "object", "description": "A single container within a `ScanGroup`.", "required": [ "container_id", "original_name", "service_name", "image", "state", "ports", "volumes", "suggested_public", "image_is_local" ], "properties": { "container_id": { "type": "string" }, "image": { "type": "string" }, "image_is_local": { "type": "boolean", "description": "True if the image was built locally (no registry host, no namespace).\nSuch projects can be imported but cannot be re-pulled by LiteBin on redeploy." }, "original_name": { "type": "string" }, "ports": { "type": "array", "items": { "$ref": "#/components/schemas/ScannedPort" } }, "service_name": { "type": "string", "description": "Service name from `com.docker.compose.service` label, or \"web\" for standalone." }, "state": { "type": "string", "description": "Docker state: \"running\", \"exited\", \"paused\", etc." }, "suggested_public": { "type": "boolean", "description": "True for the container most likely intended as the public-facing service\n(has external port bindings; lowest internal port wins if multiple qualify)." }, "volumes": { "type": "array", "items": { "$ref": "#/components/schemas/ScannedVolume" } } } }, "ScanGroup": { "type": "object", "description": "A group of foreign (non-LiteBin-managed) containers discovered during a scan.\nEither a Docker Compose project (all its services) or a single standalone container.", "required": [ "group_key", "suggested_project_id", "deploy_type", "compose_file_found", "env_file_found", "containers" ], "properties": { "compose_file_found": { "type": "boolean", "description": "True if a compose.yaml / docker-compose.yml was found at `compose_working_dir`." }, "compose_working_dir": { "type": [ "string", "null" ], "description": "Value of `com.docker.compose.project.working_dir` label, if present." }, "containers": { "type": "array", "items": { "$ref": "#/components/schemas/ScanContainer" } }, "deploy_type": { "$ref": "#/components/schemas/DeployType" }, "env_file_found": { "type": "boolean", "description": "True if a `.env` file was found at `compose_working_dir`." }, "group_key": { "type": "string", "description": "Docker Compose project name, or container name for standalone containers." }, "suggested_project_id": { "type": "string", "description": "Sanitised, DNS-safe project ID suggested for import." } } }, "ScanResult": { "type": "object", "description": "The combined result from `GET /scan?node_id=all`.", "required": [ "local", "nodes" ], "properties": { "local": { "type": "array", "items": { "$ref": "#/components/schemas/ScanGroup" } }, "nodes": { "type": "object", "description": "Map of node_id → groups found on that agent node.", "additionalProperties": { "type": "array", "items": { "$ref": "#/components/schemas/ScanGroup" } }, "propertyNames": { "type": "string" } } } }, "ScannedPort": { "type": "object", "description": "A port binding on a scanned container.", "required": [ "internal", "protocol" ], "properties": { "external": { "type": [ "integer", "null" ], "format": "int32", "description": "Host-side mapped port (None if not published).", "minimum": 0 }, "internal": { "type": "integer", "format": "int32", "description": "Container-side port.", "minimum": 0 }, "protocol": { "type": "string" } } }, "ScannedVolume": { "type": "object", "description": "A volume mount on a scanned container.", "required": [ "source", "destination", "volume_type" ], "properties": { "destination": { "type": "string", "description": "Container-side destination path." }, "source": { "type": "string", "description": "Resolved host-side source path (bind mount) or Docker volume name." }, "volume_type": { "type": "string", "description": "\"bind\" or \"volume\"." } } }, "ServiceInfo": { "type": "object", "description": "Per-service runtime info returned by the stats endpoints and embedded in\nproject responses as `public_stats` for the public service.", "required": [ "service_name", "image", "is_public", "status" ], "properties": { "cmd": { "type": [ "string", "null" ] }, "container_id": { "type": [ "string", "null" ] }, "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "cpu_percent": { "type": [ "number", "null" ], "format": "double" }, "disk_gb": { "type": [ "number", "null" ], "format": "double" }, "image": { "type": "string" }, "is_public": { "type": "boolean" }, "mapped_port": { "type": [ "integer", "null" ], "format": "int64" }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "memory_usage": { "type": [ "integer", "null" ], "format": "int64", "minimum": 0 }, "port": { "type": [ "integer", "null" ], "format": "int64" }, "ports": { "type": "array", "items": { "type": "integer", "format": "int64" }, "description": "All container ports this service exposes (compose projects), for route\nsuggestions. Empty for single-image / scan-imported services (use `port`)." }, "service_name": { "type": "string" }, "status": { "$ref": "#/components/schemas/ProjectStatus" }, "volumes": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceVolumeInfo" } } } }, "ServiceStats": { "type": "object", "required": [ "name", "memory_usage", "cpu_percent", "disk_usage" ], "properties": { "cpu_percent": { "type": "number", "format": "double" }, "disk_usage": { "type": "integer", "format": "int64", "minimum": 0 }, "memory_usage": { "type": "integer", "format": "int64", "minimum": 0 }, "name": { "type": "string" } } }, "ServiceVolumeInfo": { "type": "object", "description": "Volume attached to a project service.", "required": [ "container_path" ], "properties": { "container_path": { "type": "string" }, "volume_name": { "type": [ "string", "null" ] } } }, "SetupResponse": { "type": "object", "required": [ "needs_setup" ], "properties": { "needs_setup": { "type": "boolean" } } }, "StatsResponse": { "type": "object", "required": [ "project_id", "status" ], "properties": { "last_active_at": { "type": [ "integer", "null" ], "format": "int64" }, "project_id": { "type": "string" }, "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceInfo" } }, "status": { "type": "string" } } }, "StatusNode": { "type": "object", "required": [ "name", "status" ], "properties": { "architecture": { "type": [ "string", "null" ] }, "name": { "type": "string" }, "status": { "type": "string" }, "version": { "type": [ "string", "null" ] } } }, "StatusResponse": { "type": "object", "required": [ "version", "user", "nodes", "project_count" ], "properties": { "nodes": { "type": "array", "items": { "$ref": "#/components/schemas/StatusNode" } }, "project_count": { "type": "integer", "format": "int64" }, "user": { "$ref": "#/components/schemas/UserResponse" }, "version": { "type": "string" } } }, "SyncDnsResponse": { "type": "object", "required": [ "created", "deleted", "unchanged", "errors" ], "properties": { "created": { "type": "integer", "minimum": 0 }, "deleted": { "type": "integer", "minimum": 0 }, "errors": { "type": "integer", "minimum": 0 }, "unchanged": { "type": "integer", "minimum": 0 } } }, "SystemStatsResponse": { "type": "object", "required": [ "services" ], "properties": { "services": { "type": "array", "items": { "$ref": "#/components/schemas/ServiceStats" } } } }, "TokenScope": { "type": "string", "description": "Cumulative access level for deploy tokens. Declaration order is the rank:\n`Read < Deploy < Manage < Admin`.", "enum": [ "read", "deploy", "manage", "admin" ] }, "UnlinkQuery": { "type": "object", "properties": { "remove_files": { "type": [ "boolean", "null" ] } } }, "UpdateEnvRequest": { "type": "object", "properties": { "delete": { "type": "array", "items": { "type": "string" }, "description": "Keys to remove (applied after sets)." }, "env": { "type": "object", "description": "KEY → value pairs to set (overwrites existing). Single-line values.", "additionalProperties": { "type": "string" }, "propertyNames": { "type": "string" } }, "mode": { "$ref": "#/components/schemas/EnvUpdateMode", "description": "`merge` (default) keeps unmentioned keys; `replace` keeps exactly the sets." } } }, "UpdateEnvResponse": { "type": "object", "required": [ "project_id", "vars", "pending_apply", "message" ], "properties": { "message": { "type": "string" }, "pending_apply": { "type": "boolean" }, "project_id": { "type": "string" }, "vars": { "type": "array", "items": { "$ref": "#/components/schemas/EnvVarInfo" } } } }, "UpdateGlobalSettings": { "type": "object", "properties": { "cloudflare_api_token": { "type": [ "string", "null" ] }, "cloudflare_zone_id": { "type": [ "string", "null" ] }, "dashboard_subdomain": { "type": [ "string", "null" ] }, "default_cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "default_memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "dns_target": { "type": [ "string", "null" ] }, "domain": { "type": [ "string", "null" ], "description": "Ignored on PATCH — use /settings/domain/apply instead." }, "poke_subdomain": { "type": [ "string", "null" ] }, "routing_mode": { "type": [ "string", "null" ] } } }, "UpdateServiceSettingsRequest": { "type": "object", "properties": { "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" } } }, "UpdateSettingsRequest": { "type": "object", "properties": { "allow_raw_ports": { "type": [ "boolean", "null" ] }, "auto_start_enabled": { "type": [ "boolean", "null" ] }, "auto_stop_enabled": { "type": [ "boolean", "null" ] }, "auto_stop_timeout_mins": { "type": [ "integer", "null" ], "format": "int64" }, "cmd": { "type": [ "string", "null" ] }, "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "custom_domain": { "type": [ "string", "null" ] }, "description": { "type": [ "string", "null" ] }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "name": { "type": [ "string", "null" ] }, "port": { "type": [ "integer", "null" ], "format": "int64" } } }, "UploadQueryParams": { "type": "object", "required": [ "project_id", "image_id" ], "properties": { "image_id": { "type": "string" }, "node_id": { "type": [ "string", "null" ] }, "project_id": { "type": "string" } } }, "UploadResponse": { "type": "object", "required": [ "image_id" ], "properties": { "image_id": { "type": "string" } } }, "UserResponse": { "type": "object", "required": [ "id", "username", "is_admin" ], "properties": { "email": { "type": [ "string", "null" ] }, "id": { "type": "string" }, "is_admin": { "type": "boolean" }, "username": { "type": "string" } } }, "VolumeMount": { "type": "object", "description": "A volume mount for a container (Docker named volume or host bind mount).", "required": [ "path" ], "properties": { "name": { "type": [ "string", "null" ], "description": "Volume source name. If starts with `/` or `./`, treated as a host bind mount path.\nOtherwise treated as a Docker named volume (scoped with project_id). Defaults to project_id." }, "path": { "type": "string", "description": "Path inside the container, e.g. \"/app/uploads\"" } } }, "WhoamiResponse": { "type": "object", "description": "The active credential. `kind` is `session` (only `username` set) or\n`token` (only the token fields set).", "required": [ "kind" ], "properties": { "kind": { "type": "string", "description": "`session` or `token`." }, "name": { "type": [ "string", "null" ], "description": "Token credentials: deploy token display name." }, "project_id": { "type": [ "string", "null" ], "description": "Token credentials: project binding; absent when global." }, "scope": { "oneOf": [ { "type": "null" }, { "$ref": "#/components/schemas/TokenScope", "description": "Token credentials: cumulative access scope." } ] }, "token_id": { "type": [ "string", "null" ], "description": "Token credentials: deploy token ID." }, "username": { "type": [ "string", "null" ], "description": "Session credentials: the logged-in username." } } }, "WorkflowInputs": { "type": "object", "description": "Workflow inputs persisted as JSON in `workflow_inputs`.", "required": [ "compose_services" ], "properties": { "cmd": { "type": [ "string", "null" ] }, "compose_services": { "type": "array", "items": { "type": "string" } }, "cpu_limit": { "type": [ "number", "null" ], "format": "double" }, "dockerfile": { "type": [ "string", "null" ] }, "memory_limit_mb": { "type": [ "integer", "null" ], "format": "int64" }, "node_id": { "type": [ "string", "null" ] }, "path": { "type": [ "string", "null" ] }, "port": { "type": [ "integer", "null" ], "format": "int32", "minimum": 0 } } }, "WorkflowRun": { "type": "object", "description": "Normalized run row for the dashboard.", "required": [ "id", "run_number", "html_url" ], "properties": { "conclusion": { "type": [ "string", "null" ] }, "created_at": { "type": [ "string", "null" ] }, "head_branch": { "type": [ "string", "null" ] }, "head_sha": { "type": [ "string", "null" ] }, "html_url": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "run_number": { "type": "integer", "format": "int64" }, "status": { "type": [ "string", "null" ] }, "updated_at": { "type": [ "string", "null" ] } } } }, "securitySchemes": { "bearer_token": { "type": "http", "scheme": "bearer", "bearerFormat": "deploy token", "description": "Deploy token (CI/CD auth)" }, "session_auth": { "type": "apiKey", "in": "cookie", "name": "id", "description": "Session cookie (set on login)" } } }, "tags": [ { "name": "auth", "description": "Authentication & user management" }, { "name": "projects", "description": "Project CRUD and routes" }, { "name": "deploy", "description": "Single-service and compose deployments" }, { "name": "manage", "description": "Project lifecycle (start, stop, delete, recreate)" }, { "name": "stats", "description": "Project statistics, logs, and disk usage" }, { "name": "nodes", "description": "Node management (multi-server)" }, { "name": "settings", "description": "Project and global settings" }, { "name": "deploy-tokens", "description": "Deploy token management for CI/CD" }, { "name": "global-settings", "description": "Global platform settings and DNS" }, { "name": "backup", "description": "Platform SQLite backup (Litestream sidecar)" }, { "name": "volumes", "description": "Volume management" }, { "name": "scan", "description": "Container scanning and import" }, { "name": "env", "description": "Runtime environment variables (write-only values)" }, { "name": "github", "description": "GitHub connect-and-deploy: repo browsing, workflow links, run status" }, { "name": "health", "description": "Health checks and system stats" } ] } ``` ## CLI Reference # Command-Line Help for `l8b` This document contains the help content for the `l8b` command-line program. **Command Overview:** * [`l8b`↴](#l8b) * [`l8b deploy`↴](#l8b-deploy) * [`l8b ship`↴](#l8b-ship) * [`l8b list`↴](#l8b-list) * [`l8b logs`↴](#l8b-logs) * [`l8b url`↴](#l8b-url) * [`l8b stop`↴](#l8b-stop) * [`l8b start`↴](#l8b-start) * [`l8b restart`↴](#l8b-restart) * [`l8b delete`↴](#l8b-delete) * [`l8b init`↴](#l8b-init) * [`l8b doctor`↴](#l8b-doctor) * [`l8b mcp`↴](#l8b-mcp) * [`l8b setup`↴](#l8b-setup) * [`l8b domain`↴](#l8b-domain) * [`l8b domain set`↴](#l8b-domain-set) * [`l8b domain remove`↴](#l8b-domain-remove) * [`l8b platform`↴](#l8b-platform) * [`l8b platform domain`↴](#l8b-platform-domain) * [`l8b platform domain set`↴](#l8b-platform-domain-set) * [`l8b platform domain show`↴](#l8b-platform-domain-show) * [`l8b backup`↴](#l8b-backup) * [`l8b backup status`↴](#l8b-backup-status) * [`l8b backup enable`↴](#l8b-backup-enable) * [`l8b backup disable`↴](#l8b-backup-disable) * [`l8b backup add-s3`↴](#l8b-backup-add-s3) * [`l8b backup remove-s3`↴](#l8b-backup-remove-s3) * [`l8b backup restore`↴](#l8b-backup-restore) * [`l8b backup revert`↴](#l8b-backup-revert) * [`l8b login`↴](#l8b-login) * [`l8b logout`↴](#l8b-logout) * [`l8b status`↴](#l8b-status) * [`l8b cleanup`↴](#l8b-cleanup) * [`l8b env`↴](#l8b-env) * [`l8b env list`↴](#l8b-env-list) * [`l8b env push`↴](#l8b-env-push) * [`l8b config`↴](#l8b-config) * [`l8b config set`↴](#l8b-config-set) * [`l8b config show`↴](#l8b-config-show) ## `l8b` LiteBin CLI — deploy apps from your terminal **Usage:** `l8b [OPTIONS] ` ###### **Subcommands:** * `deploy` — Deploy the current directory to LiteBin * `ship` — Interactive deploy — guided flow for new or existing projects * `list` — List all projects with live status * `logs` — Show container logs for a project * `url` — Print the project's managed URL * `stop` — Stop a running project * `start` — Start a stopped project * `restart` — Recreate containers (applies pending .env changes) * `delete` — Delete a project, its containers, and its volumes * `init` — Write l8b.toml project defaults (and optionally the workspace .mcp.json) * `doctor` — Environment sanity checks with recovery hints * `mcp` — Run as a stdio MCP server exposing l8b as tools (for coding agents) * `setup` — First-run bootstrap: create the admin account and pair this machine * `domain` — Manage a project's custom domain * `platform` — Manage the server's platform domain (admin) * `backup` — Manage platform database backup (admin) * `login` — Log in to a LiteBin server (dashboard approval or username/password) * `logout` — Log out (remove a stored login; default: all of them) * `status` — Show CLI status and server info * `cleanup` — Clean up leftover build artifacts (temp generated dockerignore files) * `env` — Manage runtime environment variables (values are write-only) * `config` — Manage CLI configuration ###### **Options:** * `--server ` — Server URL (default: from env L8B_SERVER or stored config) * `--token ` — Deploy token (default: from env L8B_TOKEN or stored config) * `--ci` — CI mode: suppress verbose output and hide secrets (or set L8B_CI=true) * `--json` — Machine-readable JSON output (single object on stdout; or set L8B_JSON=1) ## `l8b deploy` Deploy the current directory to LiteBin **Usage:** `l8b deploy [OPTIONS]` ###### **Options:** * `--project ` — Project ID (default: `project` from l8b.toml) * `--port ` — Internal app port (first deploy only; detected from Dockerfile EXPOSE when omitted) * `--background` — Run as a background project with no managed HTTP URL * `--path ` — Path to project directory (default: current dir) Default value: `.` * `--node ` — Target node ID (optional) * `--dockerfile ` — Dockerfile path (auto-detected if not specified) * `--cmd ` — Custom command to run in the container * `--memory ` — Memory limit in MB * `--cpu ` — CPU limit (0.0 - 1.0) * `--no-auto-stop` — Disable auto-stop * `--secret ` — Include this .env file in the build (repeatable; overrides .env auto-discovery) * `--no-env` — Exclude build-time env files entirely (skip .env auto-discovery) * `--env-file ` — Push this file as runtime env after deploying (KEY=VALUE lines, merged) * `--compose` — Force compose mode (auto-detected if a compose file exists) * `--service ` — Deploy only specific services (repeatable, compose mode only) * `--grant-capability ` — Grant a project capability for this deploy (repeatable: docker-observe, host-network, raw-ports) * `--upload ` — How to upload the image to the node: auto (default, direct when the node supports it), direct (client → agent, skipping the master relay), or relay (client → master → agent) Default value: `auto` Possible values: - `auto`: Let the broker decide (direct when the node supports it, else relay/local) - `direct` - `relay` ## `l8b ship` Interactive deploy — guided flow for new or existing projects **Usage:** `l8b ship [OPTIONS]` ###### **Options:** * `--path ` — Path to project directory (default: current dir) Default value: `.` * `--port ` — App port (detected from Dockerfile EXPOSE; prompted otherwise) * `--secret ` — Include this .env file in the build (repeatable; overrides .env auto-discovery) * `--no-env` — Exclude build-time env files entirely (skip .env auto-discovery) ## `l8b list` List all projects with live status **Usage:** `l8b list` ## `l8b logs` Show container logs for a project **Usage:** `l8b logs [OPTIONS] [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ###### **Options:** * `--tail ` — Number of lines to show Default value: `100` * `--service ` — Service name (multi-service projects; defaults to the public service) * `--deploy` — Show deploy logs instead of container logs ## `l8b url` Print the project's managed URL **Usage:** `l8b url [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ## `l8b stop` Stop a running project **Usage:** `l8b stop [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ## `l8b start` Start a stopped project **Usage:** `l8b start [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ## `l8b restart` Recreate containers (applies pending .env changes) **Usage:** `l8b restart [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ## `l8b delete` Delete a project, its containers, and its volumes **Usage:** `l8b delete [OPTIONS] [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ###### **Options:** * `--yes` — Skip the confirmation prompt (required in CI/JSON mode) ## `l8b init` Write l8b.toml project defaults (and optionally the workspace .mcp.json) **Usage:** `l8b init [OPTIONS]` ###### **Options:** * `--project ` — Project ID (used as subdomain) * `--node ` — Target node ID * `--server ` — LiteBin server this repo deploys to (default: your only login) * `--mcp` — Also write a workspace .mcp.json for the litebin MCP server * `--agents` — Also add (or refresh) a LiteBin section in AGENTS.md * `--force` — Overwrite an existing l8b.toml / .mcp.json * `--path ` — Project directory (default: current dir) Default value: `.` ## `l8b doctor` Environment sanity checks with recovery hints **Usage:** `l8b doctor` ## `l8b mcp` Run as a stdio MCP server exposing l8b as tools (for coding agents) **Usage:** `l8b mcp` ## `l8b setup` First-run bootstrap: create the admin account and pair this machine **Usage:** `l8b setup --server ` ###### **Options:** * `--server ` — Server URL ## `l8b domain` Manage a project's custom domain **Usage:** `l8b domain ` ###### **Subcommands:** * `set` — Set a custom domain * `remove` — Clear the custom domain ## `l8b domain set` Set a custom domain **Usage:** `l8b domain set [OPTIONS] ` ###### **Arguments:** * `` — Custom domain (e.g. myapp.example.com) ###### **Options:** * `-p`, `--project ` — Project ID (default: l8b.toml `project`) ## `l8b domain remove` Clear the custom domain **Usage:** `l8b domain remove [OPTIONS]` ###### **Options:** * `-p`, `--project ` — Project ID (default: l8b.toml `project`) ## `l8b platform` Manage the server's platform domain (admin) **Usage:** `l8b platform ` ###### **Subcommands:** * `domain` — Change or show the platform domain (`{project}.{domain}`) ## `l8b platform domain` Change or show the platform domain (`{project}.{domain}`) **Usage:** `l8b platform domain ` ###### **Subcommands:** * `set` — Change the platform domain (old URLs stop working; you sign in again on the new host) * `show` — Show the current platform domain and dashboard URL ## `l8b platform domain set` Change the platform domain (old URLs stop working; you sign in again on the new host) **Usage:** `l8b platform domain set [OPTIONS] ` ###### **Arguments:** * `` — New platform domain (e.g. example.com or apps.example.com) ###### **Options:** * `--ack-dns` — Accept preflight DNS warnings without prompting (required in CI/JSON mode) * `--timeout ` — How long to wait for the change job, in seconds (default 300) ## `l8b platform domain show` Show the current platform domain and dashboard URL **Usage:** `l8b platform domain show` ## `l8b backup` Manage platform database backup (admin) **Usage:** `l8b backup ` ###### **Subcommands:** * `status` — Show backup status (enabled, replicas, last sync) * `enable` — Enable continuous backup (starts the Litestream sidecar) * `disable` — Disable backup (stops the sidecar; replicas are kept) * `add-s3` — Add or update an S3/R2 replica * `remove-s3` — Remove the S3/R2 replica (local backup keeps running) * `restore` — Restore the database from backup (orchestrator restarts to apply) * `revert` — Undo the last applied restore (orchestrator restarts to apply) ## `l8b backup status` Show backup status (enabled, replicas, last sync) **Usage:** `l8b backup status` ## `l8b backup enable` Enable continuous backup (starts the Litestream sidecar) **Usage:** `l8b backup enable` ## `l8b backup disable` Disable backup (stops the sidecar; replicas are kept) **Usage:** `l8b backup disable [OPTIONS]` ###### **Options:** * `--yes` — Skip confirmation (required in CI/JSON mode) ## `l8b backup add-s3` Add or update an S3/R2 replica **Usage:** `l8b backup add-s3 [OPTIONS] --endpoint --bucket --access-key --secret-key ` ###### **Options:** * `--endpoint ` — S3 endpoint (e.g. https://.r2.cloudflarestorage.com) * `--yes` — Proceed even if the destination already holds backup files (required in CI/JSON mode) * `--bucket ` — Bucket name * `--prefix ` — Key prefix (default litebin/) * `--access-key ` — Access key ID * `--secret-key ` — Secret access key ## `l8b backup remove-s3` Remove the S3/R2 replica (local backup keeps running) **Usage:** `l8b backup remove-s3 [OPTIONS]` ###### **Options:** * `--yes` — Skip confirmation (required in CI/JSON mode) ## `l8b backup restore` Restore the database from backup (orchestrator restarts to apply) **Usage:** `l8b backup restore [OPTIONS]` ###### **Options:** * `--timestamp ` — Point-in-time to restore to (RFC 3339, e.g. 2026-10-06T10:30:00Z) * `--from-local ` — Restore a local daily snapshot by date (YYYY-MM-DD) instead — bypasses R2/S3 * `--point ` — Restore a saved point by id (see `l8b backup status`) instead * `--yes` — Skip confirmation (required in CI/JSON mode) ## `l8b backup revert` Undo the last applied restore (orchestrator restarts to apply) **Usage:** `l8b backup revert [OPTIONS]` ###### **Options:** * `--yes` — Skip confirmation (required in CI/JSON mode) ## `l8b login` Log in to a LiteBin server (dashboard approval or username/password) **Usage:** `l8b login [OPTIONS] --server ` ###### **Options:** * `--server ` — Server URL * `--scope ` — Scope to request: read, deploy, manage (default), or admin — the approver decides * `--pair` — Skip the menu and pair via dashboard approval * `--password` — Skip the menu and log in with username/password (session) ## `l8b logout` Log out (remove a stored login; default: all of them) **Usage:** `l8b logout [OPTIONS]` ###### **Options:** * `--server ` — Log out of this server only ## `l8b status` Show CLI status and server info **Usage:** `l8b status [OPTIONS]` ###### **Options:** * `-p`, `--project ` — Show status of a specific project * `--wait` — Wait until the project reaches a terminal state; exit 0 only if running * `--timeout ` — Wait timeout in seconds (default 120, with --wait) * `--healthy` — Probe the project URL for a 2xx (implies --wait; skipped for background projects) ## `l8b cleanup` Clean up leftover build artifacts (temp generated dockerignore files) **Usage:** `l8b cleanup [PATH]` ###### **Arguments:** * `` — Project directory (default: current directory) Default value: `.` ## `l8b env` Manage runtime environment variables (values are write-only) **Usage:** `l8b env ` ###### **Subcommands:** * `list` — List env keys with masked previews (never values) * `push` — Push env vars from a file or stdin (never from arguments) ## `l8b env list` List env keys with masked previews (never values) **Usage:** `l8b env list [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ## `l8b env push` Push env vars from a file or stdin (never from arguments) **Usage:** `l8b env push [OPTIONS] [PROJECT]` ###### **Arguments:** * `` — Project ID (default: l8b.toml `project`) ###### **Options:** * `--file ` — Env file to push (default: .env in the current directory) * `--stdin` — Read env content from stdin instead of a file (e.g. `sops -d … | l8b env push --stdin`) * `--replace` — Replace the whole env file instead of merging into it * `--apply` — Recreate the container after pushing to apply changes ## `l8b config` Manage CLI configuration **Usage:** `l8b config ` ###### **Subcommands:** * `set` — Set configuration values * `show` — Show current configuration ## `l8b config set` Set configuration values **Usage:** `l8b config set [OPTIONS]` ###### **Options:** * `--server ` — Server URL * `--token ` — Deploy token ## `l8b config show` Show current configuration **Usage:** `l8b config show`
This document was generated automatically by clap-markdown.