{
  "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 `<replica>/ltx/<level>/<minTXID>-<maxTXID>.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"
    }
  ]
}
