openapi: 3.1.0 info: title: Sencho API version: 0.25.3 description: | REST API for Sencho, a self-hosted Docker Compose management dashboard. Sencho exposes a public API for automating stack deployments, managing webhooks, monitoring fleet health, and integrating with CI/CD pipelines. ## Authentication All authenticated endpoints accept a Bearer token in the `Authorization` header. Generate API tokens from **Settings > API Tokens** in the Sencho dashboard (admin role required). ``` Authorization: Bearer YOUR_API_TOKEN ``` ## Node Routing Sencho manages multiple nodes (local and remote). To target a specific node, include the `x-node-id` header or `?nodeId=` query parameter. If omitted, the request targets the default node. ## License Tiers Some endpoints require an Admiral license. Requests to gated endpoints on Community return `403` with `code: "PAID_REQUIRED"`. contact: name: Sencho url: https://sencho.io license: name: BSL 1.1 url: https://github.com/studio-saelix/sencho/blob/main/LICENSE servers: - url: "{protocol}://{host}:{port}" description: Your Sencho instance variables: protocol: default: https enum: [http, https] host: default: localhost port: default: "1852" security: - bearerAuth: [] tags: - name: Health description: Instance health check - name: Stacks description: Create, read, update, delete, and operate Docker Compose stacks - name: Containers description: List and manage running containers - name: API Tokens description: Manage scoped API tokens (admin role required) - name: Webhooks description: Configure and trigger deployment webhooks - name: Nodes description: Manage local and remote Sencho nodes - name: Fleet description: Multi-node fleet overview and snapshots - name: Scheduled Tasks description: Configure recurring automated operations (admin role required) - name: Registries description: Manage private container registry credentials (Admiral license required). These endpoints are only accessible via browser sessions — API tokens receive `SCOPE_DENIED`. - name: Image Updates description: Check for available container image updates across stacks components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: API token generated from Settings > API Tokens. Scopes — `read-only`, `deploy-only`, or `full-admin`. webhookSignature: type: apiKey in: header name: X-Webhook-Signature description: HMAC-SHA256 signature for webhook trigger requests. Compute as `sha256=HMAC(request_body, webhook_secret)`. parameters: stackName: name: stackName in: path required: true description: > Stack directory name. Must match `^[a-zA-Z0-9_-]+$` (alphanumeric characters, hyphens, and underscores only). Returns `400 Invalid stack name` if the name contains path separators, dots, spaces, or other special characters. schema: type: string pattern: '^[a-zA-Z0-9_-]+$' example: my-stack nodeId: name: x-node-id in: header required: false description: Target a specific node by ID. If omitted, the default node is used. Can also be passed as `?nodeId=` query parameter. schema: type: integer example: 1 nodeIdQuery: name: nodeId in: query required: false description: Target a specific node by ID (alternative to `x-node-id` header). schema: type: integer example: 1 idPath: name: id in: path required: true description: Numeric resource ID. schema: type: integer schemas: Error: type: object required: [error] properties: error: type: string description: Human-readable error message. code: type: string description: Machine-readable error code (e.g., `PAID_REQUIRED`, `SCOPE_DENIED`). enum: [PAID_REQUIRED, SCOPE_DENIED] SuccessMessage: type: object required: [message] properties: message: type: string SuccessBoolean: type: object required: [success] properties: success: type: boolean example: true Container: type: object properties: Id: type: string description: Docker container ID. Names: type: array items: type: string description: Container names (prefixed with `/`). Service: type: string description: Compose service name (if part of a stack). State: type: string description: Container state. enum: [running, exited, paused, restarting, dead, created] Status: type: string description: Human-readable status string (e.g., "Up 2 hours"). Ports: type: array items: type: object properties: PrivatePort: type: integer PublicPort: type: integer Node: type: object required: [id, name, type, compose_dir, is_default, created_at] properties: id: type: integer name: type: string type: type: string enum: [local, remote] compose_dir: type: string description: Base directory for Docker Compose stacks on this node. is_default: type: integer enum: [0, 1] description: Whether this is the default node (1 = true). api_url: type: string description: Remote node API URL (only for remote nodes). api_token: type: string description: Redacted API token for remote node authentication. created_at: type: integer description: Unix timestamp (seconds). Webhook: type: object required: [id, name, stack_name, action, secret, enabled, created_at] properties: id: type: integer node_id: type: integer description: Node the webhook executes against. name: type: string stack_name: type: string action: type: string enum: [deploy, restart, stop, start, pull, git-pull] secret: type: string description: Masked secret (e.g., `****...****`). Full secret is only returned on creation. enabled: type: integer enum: [0, 1] created_at: type: integer description: Unix timestamp (seconds). WebhookExecution: type: object properties: id: type: integer webhook_id: type: integer triggered_at: type: integer description: Unix timestamp (seconds). status: type: string response: type: string error: type: ["string", "null"] ApiToken: type: object required: [id, name, scope, user_id, created_at] properties: id: type: integer name: type: string scope: type: string enum: [read-only, deploy-only, full-admin] user_id: type: integer created_at: type: integer description: Unix timestamp (seconds). expires_at: type: ["integer", "null"] description: Unix timestamp (seconds) or null for no expiry. revoked_at: type: ["integer", "null"] last_used_at: type: ["integer", "null"] FleetNodeOverview: type: object properties: id: type: integer name: type: string type: type: string enum: [local, remote] status: type: string enum: [online, offline] stats: type: ["object", "null"] description: Container statistics for the node. systemStats: type: ["object", "null"] description: Host system resource statistics. stacks: type: ["integer", "null"] description: Number of stacks on this node. FleetSnapshot: type: object required: [id, description, created_by, created_at, node_count, stack_count] properties: id: type: integer description: type: string created_by: type: string created_at: type: integer description: Unix timestamp (seconds). node_count: type: integer stack_count: type: integer skipped_nodes: type: string description: JSON-stringified array of skipped node names. FleetSnapshotDetail: allOf: - $ref: "#/components/schemas/FleetSnapshot" - type: object properties: nodes: type: array items: type: object properties: nodeId: type: integer nodeName: type: string stacks: type: array items: type: object properties: stackName: type: string files: type: array items: type: object properties: filename: type: string content: type: string ScheduledTask: type: object required: [id, name, target_type, action, cron_expression, enabled, created_by, created_at, updated_at] properties: id: type: integer name: type: string target_type: type: string enum: [stack, fleet, system] target_id: type: ["string", "null"] description: Stack name (when target_type is `stack`). node_id: type: ["integer", "null"] description: Target node ID (when target_type is `stack`). action: type: string enum: [restart, snapshot, prune] cron_expression: type: string description: Standard cron expression (5 fields). example: "0 3 * * *" enabled: type: integer enum: [0, 1] created_by: type: string created_at: type: integer description: Unix timestamp (seconds). updated_at: type: integer description: Unix timestamp (seconds). last_run_at: type: ["integer", "null"] next_run_at: type: ["integer", "null"] last_status: type: ["string", "null"] last_error: type: ["string", "null"] prune_targets: type: ["string", "null"] description: JSON-stringified array of prune targets (containers, images, networks, volumes). target_services: type: ["string", "null"] description: JSON-stringified array of service names to restart. prune_label_filter: type: ["string", "null"] description: Docker label filter for prune operations. ScheduledTaskRun: type: object required: [id, task_id, started_at, status, triggered_by] properties: id: type: integer task_id: type: integer started_at: type: integer description: Unix timestamp (seconds). completed_at: type: ["integer", "null"] triggered_by: type: string enum: [scheduled, manual] status: type: string enum: [pending, running, success, failed] output: type: ["string", "null"] error: type: ["string", "null"] Registry: type: object properties: id: type: integer name: type: string example: GitHub Container Registry url: type: string example: ghcr.io type: type: string enum: [dockerhub, ghcr, ecr, custom] username: type: string aws_region: type: ["string", "null"] description: AWS region (only present for ECR registries). created_at: type: integer description: Unix timestamp (seconds). RegistryCreate: type: object required: [name, url, type, username, secret] properties: name: type: string maxLength: 100 example: GitHub Container Registry url: type: string maxLength: 500 example: ghcr.io type: type: string enum: [dockerhub, ghcr, ecr, custom] username: type: string example: my-user secret: type: string description: Password, token, or access key. Write-only — never returned in GET responses. aws_region: type: string description: Required when `type` is `ecr`. example: us-east-1 ImageUpdateStatus: type: object properties: stack_name: type: string description: Name of the stack. has_updates: type: integer enum: [0, 1] description: Whether any images in this stack have newer versions available. last_checked: type: ["integer", "null"] description: Unix timestamp of the last check. responses: Unauthorized: description: Authentication required. Provide a valid Bearer token. content: application/json: schema: $ref: "#/components/schemas/Error" example: error: "Authentication required" Forbidden: description: Insufficient permissions or license tier. content: application/json: schema: $ref: "#/components/schemas/Error" example: error: "This feature requires a Sencho Admiral license." code: "PAID_REQUIRED" NotFound: description: Resource not found. content: application/json: schema: $ref: "#/components/schemas/Error" InternalError: description: Internal server error. content: application/json: schema: $ref: "#/components/schemas/Error" paths: # ── Health ────────────────────────────────────────────── /api/health: get: operationId: getHealth tags: [Health] summary: Health check description: Returns instance health status and uptime. No authentication required. Useful for uptime monitors and Docker HEALTHCHECK. security: [] responses: "200": description: Instance is healthy. content: application/json: schema: type: object required: [status, uptime] properties: status: type: string example: ok uptime: type: number description: Process uptime in seconds. /api/meta: get: operationId: getMeta tags: [Health] summary: Instance metadata description: Returns this Sencho instance's version and list of supported capabilities. No authentication required. Used by nodes to negotiate feature compatibility. security: [] responses: "200": description: Instance metadata. content: application/json: schema: type: object required: [version, capabilities] properties: version: type: string description: Sencho version (semver). example: "0.32.0" capabilities: type: array description: List of feature capabilities this instance supports. items: type: string example: ["stacks", "containers", "fleet", "auto-updates", "host-console"] /api/system/update: post: operationId: triggerSelfUpdate tags: [Health] summary: Trigger self-update description: | Instructs this Sencho instance to pull the latest Docker image and recreate its own container. Returns 202 immediately; the actual update happens asynchronously after the response is sent. Requires the instance to be deployed via Docker Compose with the Docker socket mounted. responses: "202": description: Update initiated. content: application/json: schema: type: object properties: message: type: string example: "Update initiated. The server will restart shortly." "401": $ref: "#/components/responses/Unauthorized" "503": description: Self-update not available (not running in Docker Compose). content: application/json: schema: $ref: "#/components/schemas/Error" # ── Stacks ────────────────────────────────────────────── /api/stacks: get: operationId: listStacks tags: [Stacks] summary: List all stacks description: Returns an array of stack directory names on the target node. parameters: - $ref: "#/components/parameters/nodeId" - $ref: "#/components/parameters/nodeIdQuery" responses: "200": description: Array of stack names. content: application/json: schema: type: array items: type: string example: ["traefik", "portainer", "monitoring"] "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: operationId: createStack tags: [Stacks] summary: Create a new stack description: Creates a new stack directory with a default `compose.yaml` file. parameters: - $ref: "#/components/parameters/nodeId" requestBody: required: true content: application/json: schema: type: object required: [stackName] properties: stackName: type: string description: Stack name. Alphanumeric characters, hyphens, and underscores only. pattern: "^[a-zA-Z0-9_-]+$" example: my-new-stack responses: "201": description: Stack created. content: application/json: schema: type: object required: [message, name] properties: message: type: string example: Stack created successfully name: type: string example: my-new-stack "400": description: Invalid stack name. content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: Stack already exists. content: application/json: schema: $ref: "#/components/schemas/Error" example: error: Stack already exists "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}: get: operationId: getStack tags: [Stacks] summary: Get stack compose file description: Returns the raw `compose.yaml` content for the specified stack. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Raw compose.yaml content. content: text/plain: schema: type: string "400": description: Invalid stack name. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" put: operationId: updateStack tags: [Stacks] summary: Update stack compose file description: Overwrites the `compose.yaml` content for the specified stack. Requires `stack:edit` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" requestBody: required: true content: application/json: schema: type: object required: [content] properties: content: type: string description: Full compose.yaml content. responses: "200": description: Stack saved. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Stack saved successfully "400": description: Invalid stack name or content. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteStack tags: [Stacks] summary: Delete stack description: Permanently deletes the stack directory and all its files. Requires `stack:delete` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Stack deleted. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Stack deleted successfully "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/envs: get: operationId: getStackEnvFiles tags: [Stacks] summary: List env file paths description: Returns the list of environment file paths referenced in the stack's compose file (`env_file` directives). parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: List of env file paths. content: application/json: schema: type: object required: [envFiles] properties: envFiles: type: array items: type: string example: [".env", "config/database.env"] "400": description: Invalid stack name. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/env: get: operationId: getStackEnv tags: [Stacks] summary: Get env file content description: Returns the content of an environment file. Defaults to `.env` if no `file` query parameter is specified. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" - name: file in: query required: false description: Path to a specific env file (must be in the resolved env files list). schema: type: string responses: "200": description: Raw env file content (KEY=VALUE format). content: text/plain: schema: type: string "400": description: Invalid stack name or disallowed file path. content: application/json: schema: $ref: "#/components/schemas/Error" "404": description: Env file not found. content: application/json: schema: $ref: "#/components/schemas/Error" example: error: Env file not found "500": $ref: "#/components/responses/InternalError" put: operationId: updateStackEnv tags: [Stacks] summary: Update env file description: Overwrites an environment file's content. Requires `stack:edit` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" - name: file in: query required: false description: Path to a specific env file to update. schema: type: string requestBody: required: true content: application/json: schema: type: object required: [content] properties: content: type: string description: Full env file content (KEY=VALUE format). responses: "200": description: Env file saved. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Env file saved successfully "400": description: Invalid input. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/containers: get: operationId: getStackContainers tags: [Stacks] summary: List stack containers description: Returns all containers belonging to the specified stack. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Array of container objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/Container" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/services: get: operationId: getStackServices tags: [Stacks] summary: List stack services description: Returns the service names defined in the stack's compose file. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Array of service names. content: application/json: schema: type: array items: type: string example: ["web", "db", "redis"] "400": description: Invalid stack name. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/deploy: post: operationId: deployStack tags: [Stacks] summary: Deploy stack description: | Runs `docker compose up -d` for the stack using atomic deployment with automatic rollback on failure. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Stack deployed successfully. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Deployed successfully "403": $ref: "#/components/responses/Forbidden" "500": description: Deployment failed. content: application/json: schema: type: object required: [error] properties: error: type: string rolledBack: type: boolean description: Whether the stack was automatically rolled back. /api/stacks/{stackName}/down: post: operationId: downStack tags: [Stacks] summary: Tear down stack description: Runs `docker compose down` for the stack, removing containers and networks. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Command started. content: application/json: schema: type: object required: [status] properties: status: type: string example: Command started "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/start: post: operationId: startStack tags: [Stacks] summary: Start stack containers description: Starts all stopped containers in the stack via the Docker Engine API. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Containers started. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "403": $ref: "#/components/responses/Forbidden" "404": description: No containers found for this stack. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/stop: post: operationId: stopStack tags: [Stacks] summary: Stop stack containers description: Stops all running containers in the stack via the Docker Engine API. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Containers stopped. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "403": $ref: "#/components/responses/Forbidden" "404": description: No containers found for this stack. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/restart: post: operationId: restartStack tags: [Stacks] summary: Restart stack containers description: Restarts all containers in the stack via the Docker Engine API. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Containers restarted. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "403": $ref: "#/components/responses/Forbidden" "404": description: No containers found for this stack. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/update: post: operationId: updateStackImages tags: [Stacks] summary: Pull and recreate stack description: | Pulls latest images and recreates containers (`docker compose pull && up -d`) using atomic update with automatic rollback on failure. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Update completed. content: application/json: schema: type: object required: [status] properties: status: type: string example: Update completed "403": $ref: "#/components/responses/Forbidden" "500": description: Update failed. content: application/json: schema: type: object required: [error] properties: error: type: string rolledBack: type: boolean /api/stacks/{stackName}/rollback: post: operationId: rollbackStack tags: [Stacks] summary: Rollback stack description: Restores the stack to its previous deployment state. Requires `stack:deploy` permission. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Stack rolled back. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Stack rolled back successfully. "403": $ref: "#/components/responses/Forbidden" "404": description: No backup available. content: application/json: schema: $ref: "#/components/schemas/Error" example: error: No backup available for this stack. "500": $ref: "#/components/responses/InternalError" /api/stacks/{stackName}/backup: get: operationId: getStackBackupStatus tags: [Stacks] summary: Check backup status description: Returns whether a deployment backup exists for this stack and when it was created. parameters: - $ref: "#/components/parameters/stackName" - $ref: "#/components/parameters/nodeId" responses: "200": description: Backup status. content: application/json: schema: type: object required: [exists] properties: exists: type: boolean timestamp: type: ["integer", "null"] description: Unix timestamp of the backup, or null if no backup exists. "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" # ── Containers ────────────────────────────────────────── /api/containers: get: operationId: listContainers tags: [Containers] summary: List all containers description: Returns all running containers on the target node. parameters: - $ref: "#/components/parameters/nodeId" - $ref: "#/components/parameters/nodeIdQuery" responses: "200": description: Array of container objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/Container" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /api/containers/{id}/logs: get: operationId: streamContainerLogs tags: [Containers] summary: Stream container logs description: | Streams container logs as Server-Sent Events (SSE). The connection remains open and pushes new log lines as `data:` events in real time. parameters: - $ref: "#/components/parameters/idPath" - $ref: "#/components/parameters/nodeId" responses: "200": description: SSE log stream. content: text/event-stream: schema: type: string description: Each event is a `data:` line containing a JSON-encoded log string. "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /api/containers/{id}/start: post: operationId: startContainer tags: [Containers] summary: Start container description: Starts a stopped container. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" - $ref: "#/components/parameters/nodeId" responses: "200": description: Container started. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Container started "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/containers/{id}/stop: post: operationId: stopContainer tags: [Containers] summary: Stop container description: Stops a running container. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" - $ref: "#/components/parameters/nodeId" responses: "200": description: Container stopped. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Container stopped "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/containers/{id}/restart: post: operationId: restartContainer tags: [Containers] summary: Restart container description: Restarts a container. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" - $ref: "#/components/parameters/nodeId" responses: "200": description: Container restarted. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Container restarted "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" # ── API Tokens ────────────────────────────────────────── /api/api-tokens: post: operationId: createApiToken tags: [API Tokens] summary: Create API token description: | Generates a new scoped API token. The full token is only returned in the creation response and cannot be retrieved again. Requires admin role. **Note:** API tokens cannot create other API tokens. responses: "201": description: Token created. The `token` field contains the full JWT. Save it now; it will not be shown again. content: application/json: schema: type: object required: [id, token] properties: id: type: integer token: type: string description: Full JWT token. Store securely — this is the only time it's returned. "400": description: Validation error (missing/invalid fields, or maximum of 25 active tokens reached). content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "409": description: An active token with this name already exists. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" requestBody: required: true content: application/json: schema: type: object required: [name, scope] properties: name: type: string description: Human-readable token name. Must be unique among the user's active tokens. maxLength: 100 example: CI/CD Deploy Token scope: type: string description: | Permission scope: - `read-only` — GET requests only - `deploy-only` — Read + stack deploy/stop/restart operations - `full-admin` — All operations (except license, user, and token management) enum: [read-only, deploy-only, full-admin] expires_in: type: ["integer", "null"] description: Token lifetime in days. Use `null` for no expiry. enum: [30, 60, 90, 365, null] example: 90 get: operationId: listApiTokens tags: [API Tokens] summary: List API tokens description: | Returns all API tokens for the current user. Token hashes are never exposed. Requires admin role. **Note:** API tokens cannot list other API tokens. responses: "200": description: Array of token metadata. content: application/json: schema: type: array items: $ref: "#/components/schemas/ApiToken" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/api-tokens/{id}: delete: operationId: revokeApiToken tags: [API Tokens] summary: Revoke API token description: | Permanently revokes an API token. Users can only revoke their own tokens. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Token revoked. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "400": description: Invalid token ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ── Webhooks ──────────────────────────────────────────── /api/webhooks: get: operationId: listWebhooks tags: [Webhooks] summary: List webhooks description: Returns all configured webhooks with masked secrets. responses: "200": description: Array of webhook objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/Webhook" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" post: operationId: createWebhook tags: [Webhooks] summary: Create webhook description: | Creates a new webhook for a stack. The webhook secret is auto-generated and only returned in the creation response. Requires admin role. requestBody: required: true content: application/json: schema: type: object required: [name, stack_name, action] properties: name: type: string maxLength: 100 example: GitHub Deploy Hook stack_name: type: string description: Target stack name. example: my-app action: type: string enum: [deploy, restart, stop, start, pull, git-pull] description: Action to execute when the webhook is triggered. `git-pull` requires a Git source attached to the stack. enabled: type: boolean default: true node_id: type: integer description: Node the webhook executes against. Defaults to the request's resolved node or the default node. responses: "201": description: Webhook created. The `secret` field is the HMAC signing key; save it now. content: application/json: schema: type: object required: [id, secret] properties: id: type: integer secret: type: string description: HMAC-SHA256 signing secret. Store securely; this is the only time it's returned in full. "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/webhooks/{id}: put: operationId: updateWebhook tags: [Webhooks] summary: Update webhook description: Updates webhook configuration. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" requestBody: required: true content: application/json: schema: type: object properties: name: type: string maxLength: 100 stack_name: type: string action: type: string enum: [deploy, restart, stop, start, pull, git-pull] enabled: type: boolean node_id: type: integer description: Retarget the webhook to a different node. responses: "200": description: Webhook updated. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteWebhook tags: [Webhooks] summary: Delete webhook description: Permanently deletes a webhook. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Webhook deleted. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/webhooks/{id}/history: get: operationId: getWebhookHistory tags: [Webhooks] summary: Get webhook execution history description: Returns the execution log for a webhook. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Array of execution records. content: application/json: schema: type: array items: $ref: "#/components/schemas/WebhookExecution" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/webhooks/{id}/trigger: post: operationId: triggerWebhook tags: [Webhooks] summary: Trigger webhook description: | Externally triggers a webhook action. This endpoint is public but requires a valid HMAC-SHA256 signature in the `X-Webhook-Signature` header. Sign the exact raw bytes of the request body; an empty body is rejected. Compute the signature as: `sha256=` + HMAC-SHA256(raw_request_body, webhook_secret). Every unauthenticated rejection (unknown id, disabled webhook, non-paid licence, missing or invalid signature, empty body) returns the same `404` response so callers cannot enumerate webhook ids or fingerprint the instance's licence tier. security: - webhookSignature: [] parameters: - $ref: "#/components/parameters/idPath" requestBody: required: true content: application/json: schema: type: object properties: action: type: string enum: [deploy, restart, stop, start, pull, git-pull] description: Override the default webhook action. Must be one of the allowed actions. responses: "202": description: Webhook accepted and action queued. content: application/json: schema: type: object required: [message, action] properties: message: type: string example: Webhook accepted action: type: string example: deploy "400": description: Authentication succeeded but the body's `action` override was not in the allowlist. content: application/json: schema: $ref: "#/components/schemas/Error" "404": description: Authentication failed. The webhook is unknown, disabled, the licence is not paid, the signature header is missing, the body was empty, or the signature did not match. Sencho returns the same response for every unauthenticated case. content: application/json: schema: $ref: "#/components/schemas/Error" "429": description: Rate limit exceeded. The trigger endpoint allows 500 requests per minute per source IP; CI/CD callers behind shared NAT may share that budget. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" # ── Nodes ─────────────────────────────────────────────── /api/nodes: get: operationId: listNodes tags: [Nodes] summary: List all nodes description: Returns all registered nodes (local and remote). responses: "200": description: Array of node objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/Node" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" post: operationId: createNode tags: [Nodes] summary: Register a new node description: | Adds a new local or remote node. Remote nodes require an API URL pointing to another Sencho instance and an API token for authentication. Requires `node:manage` permission. **Note:** API tokens cannot manage nodes. requestBody: required: true content: application/json: schema: type: object required: [name, type] properties: name: type: string example: production-server type: type: string enum: [local, remote] compose_dir: type: string description: Base directory for Compose stacks (defaults to `/app/compose`). is_default: type: boolean description: Set as the default node. api_url: type: string description: Remote Sencho instance URL (required for remote nodes). example: https://sencho.example.com:1852 api_token: type: string description: API token for authenticating with the remote Sencho instance. responses: "200": description: Node created. content: application/json: schema: type: object required: [success, id] properties: success: type: boolean id: type: integer description: New node ID. warning: type: string description: Warning if the remote URL uses plain HTTP. "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "409": description: Node name already exists. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/nodes/{id}: get: operationId: getNode tags: [Nodes] summary: Get node details parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Node object. content: application/json: schema: $ref: "#/components/schemas/Node" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" put: operationId: updateNode tags: [Nodes] summary: Update node description: Updates node configuration. Requires `node:manage` permission. parameters: - $ref: "#/components/parameters/idPath" requestBody: required: true content: application/json: schema: type: object properties: name: type: string compose_dir: type: string is_default: type: boolean api_url: type: string api_token: type: string responses: "200": description: Node updated. content: application/json: schema: type: object required: [success] properties: success: type: boolean warning: type: string "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteNode tags: [Nodes] summary: Delete node description: Removes a node from the fleet. Requires `node:manage` permission. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Node deleted. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/nodes/{id}/test: post: operationId: testNodeConnection tags: [Nodes] summary: Test node connection description: Tests connectivity to a node. For remote nodes, pings the remote Sencho instance's health endpoint. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Connection test result. content: application/json: schema: type: object required: [success] properties: success: type: boolean message: type: string error: type: string "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /api/nodes/{id}/meta: get: operationId: getNodeMeta tags: [Nodes] summary: Get node capabilities description: "Returns the version and supported capabilities for a specific node. For local nodes, returns this instance's own metadata. For remote nodes, relays the response from the remote instance's `/api/meta` endpoint. Returns `{ version: null, capabilities: [] }` if the remote node is offline or doesn't support the meta endpoint." parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Node capability metadata. content: application/json: schema: type: object required: [version, capabilities] properties: version: type: string nullable: true description: Sencho version of the node, or null if unavailable. example: "0.31.0" capabilities: type: array description: List of feature capabilities the node supports. Empty array if unavailable. items: type: string example: ["stacks", "containers", "fleet"] "401": $ref: "#/components/responses/Unauthorized" "404": $ref: "#/components/responses/NotFound" # ── Fleet ─────────────────────────────────────────────── /api/fleet/overview: get: operationId: getFleetOverview tags: [Fleet] summary: Fleet overview description: Returns an aggregated overview of all nodes including status, container stats, and stack counts. responses: "200": description: Array of fleet node overviews. content: application/json: schema: type: array items: $ref: "#/components/schemas/FleetNodeOverview" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /api/fleet/node/{nodeId}/stacks: get: operationId: getFleetNodeStacks tags: [Fleet] summary: List stacks on a fleet node description: Returns stack names from a specific fleet node. parameters: - name: nodeId in: path required: true schema: type: integer responses: "200": description: Array of stack names. content: application/json: schema: type: array items: type: string "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" "502": description: Failed to reach remote node. content: application/json: schema: $ref: "#/components/schemas/Error" "503": description: Remote node not configured. content: application/json: schema: $ref: "#/components/schemas/Error" /api/fleet/node/{nodeId}/stacks/{stackName}/containers: get: operationId: getFleetNodeStackContainers tags: [Fleet] summary: List containers in a fleet node stack description: Returns containers for a specific stack on a specific fleet node. parameters: - name: nodeId in: path required: true schema: type: integer - $ref: "#/components/parameters/stackName" responses: "200": description: Array of container objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/Container" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" "503": description: Remote node not configured. content: application/json: schema: $ref: "#/components/schemas/Error" /api/fleet/update-status: get: operationId: getFleetUpdateStatus tags: [Fleet] summary: Fleet update status description: Returns version comparison and active update status for all nodes. Compares each node's version against the gateway's version. responses: "200": description: Update status for all nodes. content: application/json: schema: type: object properties: nodes: type: array items: type: object properties: nodeId: type: integer name: type: string type: type: string enum: [local, remote] version: type: string nullable: true latestVersion: type: string updateAvailable: type: boolean updateStatus: type: string nullable: true enum: [updating, completed, timeout, failed, null] "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" /api/fleet/nodes/{nodeId}/update: post: operationId: triggerNodeUpdate tags: [Fleet] summary: Trigger node update description: Initiates a self-update on the specified node. For remote nodes, sends the update command via the proxy. For local nodes, triggers the update directly. parameters: - name: nodeId in: path required: true schema: type: integer responses: "202": description: Update initiated. content: application/json: schema: type: object properties: message: type: string "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" "404": description: Node not found. content: application/json: schema: $ref: "#/components/schemas/Error" "409": description: Update already in progress. content: application/json: schema: $ref: "#/components/schemas/Error" "503": description: Self-update not available on the target node. content: application/json: schema: $ref: "#/components/schemas/Error" /api/fleet/update-all: post: operationId: triggerFleetUpdateAll tags: [Fleet] summary: Update all outdated nodes description: Triggers self-update on all remote nodes running an older version than the gateway. Local nodes are excluded from bulk updates. responses: "202": description: Bulk update initiated. content: application/json: schema: type: object properties: updating: type: array items: type: string description: Names of nodes where update was triggered. skipped: type: array items: type: string description: Names of nodes that were skipped. "401": $ref: "#/components/responses/Unauthorized" "403": $ref: "#/components/responses/Forbidden" /api/fleet/snapshots: post: operationId: createFleetSnapshot tags: [Fleet] summary: Create fleet snapshot description: Creates a point-in-time backup of all compose files across all nodes. Requires admin role. requestBody: required: false content: application/json: schema: type: object properties: description: type: string description: Optional description for the snapshot. example: Pre-migration backup responses: "201": description: Snapshot created. content: application/json: schema: $ref: "#/components/schemas/FleetSnapshot" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" get: operationId: listFleetSnapshots tags: [Fleet] summary: List fleet snapshots description: Returns paginated fleet snapshots. parameters: - name: limit in: query schema: type: integer default: 50 maximum: 100 - name: offset in: query schema: type: integer default: 0 responses: "200": description: Paginated snapshot list. content: application/json: schema: type: object required: [snapshots, total] properties: snapshots: type: array items: $ref: "#/components/schemas/FleetSnapshot" total: type: integer "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/fleet/snapshots/{id}: get: operationId: getFleetSnapshot tags: [Fleet] summary: Get snapshot details description: Returns full snapshot details including all captured files grouped by node and stack. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Snapshot with file contents. content: application/json: schema: $ref: "#/components/schemas/FleetSnapshotDetail" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteFleetSnapshot tags: [Fleet] summary: Delete snapshot description: Permanently deletes a fleet snapshot. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Snapshot deleted. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/fleet/snapshots/{id}/restore: post: operationId: restoreFleetSnapshot tags: [Fleet] summary: Restore from snapshot description: Restores a specific stack on a specific node from the snapshot. Optionally redeploys after restore. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" requestBody: required: true content: application/json: schema: type: object required: [nodeId, stackName] properties: nodeId: type: integer description: Target node ID to restore to. stackName: type: string description: Stack to restore. redeploy: type: boolean default: false description: Whether to redeploy the stack after restoring files. responses: "200": description: Stack restored. content: application/json: schema: $ref: "#/components/schemas/SuccessMessage" example: message: Stack restored successfully. "400": description: Missing required fields. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ── Scheduled Tasks ───────────────────────────────────── /api/scheduled-tasks: get: operationId: listScheduledTasks tags: [Scheduled Tasks] summary: List scheduled tasks description: Returns all scheduled tasks. Requires admin role. responses: "200": description: Array of scheduled task objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/ScheduledTask" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" post: operationId: createScheduledTask tags: [Scheduled Tasks] summary: Create scheduled task description: | Creates a new recurring task. Action-target rules: - `restart` requires `target_type: stack` (with `target_id` and `node_id`) - `snapshot` requires `target_type: fleet` - `prune` requires `target_type: system` Requires admin role. requestBody: required: true content: application/json: schema: type: object required: [name, target_type, action, cron_expression] properties: name: type: string example: Nightly restart target_type: type: string enum: [stack, fleet, system] target_id: type: string description: Stack name (required when target_type is `stack`). node_id: type: integer description: Target node ID (required when target_type is `stack`). action: type: string enum: [restart, snapshot, prune] cron_expression: type: string description: Standard 5-field cron expression. example: "0 3 * * *" enabled: type: boolean default: true prune_targets: type: array items: type: string enum: [containers, images, networks, volumes] description: Resources to prune (only for `prune` action). target_services: type: array items: type: string description: Specific services to restart (only for `restart` action). prune_label_filter: type: string description: Docker label filter for prune operations. responses: "201": description: Task created. content: application/json: schema: $ref: "#/components/schemas/ScheduledTask" "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/scheduled-tasks/{id}: get: operationId: getScheduledTask tags: [Scheduled Tasks] summary: Get scheduled task parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Scheduled task object. content: application/json: schema: $ref: "#/components/schemas/ScheduledTask" "400": description: Invalid task ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" put: operationId: updateScheduledTask tags: [Scheduled Tasks] summary: Update scheduled task description: Updates task configuration. Same validation rules as creation apply. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" requestBody: required: true content: application/json: schema: type: object properties: name: type: string target_type: type: string enum: [stack, fleet, system] target_id: type: string node_id: type: integer action: type: string enum: [restart, snapshot, prune] cron_expression: type: string enabled: type: boolean prune_targets: type: array items: type: string target_services: type: array items: type: string prune_label_filter: type: string responses: "200": description: Task updated. content: application/json: schema: $ref: "#/components/schemas/ScheduledTask" "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteScheduledTask tags: [Scheduled Tasks] summary: Delete scheduled task description: Permanently deletes a scheduled task. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Task deleted. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "400": description: Invalid task ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/scheduled-tasks/{id}/toggle: patch: operationId: toggleScheduledTask tags: [Scheduled Tasks] summary: Toggle task enabled/disabled description: Flips the enabled state of a scheduled task. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Task toggled. Returns updated task. content: application/json: schema: $ref: "#/components/schemas/ScheduledTask" "400": description: Invalid task ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/scheduled-tasks/{id}/run: post: operationId: runScheduledTask tags: [Scheduled Tasks] summary: Run task immediately description: Executes the scheduled task immediately, regardless of its cron schedule. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Task executed. Returns updated task with `last_run_at`. content: application/json: schema: $ref: "#/components/schemas/ScheduledTask" "400": description: Invalid task ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/scheduled-tasks/{id}/runs: get: operationId: listScheduledTaskRuns tags: [Scheduled Tasks] summary: List task execution history description: Returns paginated execution history for a scheduled task. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: "200": description: Array of task run records. content: application/json: schema: type: array items: $ref: "#/components/schemas/ScheduledTaskRun" "400": description: Invalid task ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/scheduled-tasks/{id}/runs/export: get: operationId: exportScheduledTaskRuns tags: [Scheduled Tasks] summary: Export task history as CSV description: Downloads the execution history for a scheduled task as a CSV file. Requires admin role. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: CSV file download. content: text/csv: schema: type: string description: "CSV with columns: Timestamp, Source, Status, Duration (s), Details" "400": description: Invalid task ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" # ── Registries ────────────────────────────────────────── /api/registries: get: operationId: listRegistries tags: [Registries] summary: List registries description: | Returns all configured private container registries. Secrets are never included in the response. Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`. responses: "200": description: Array of registry objects. content: application/json: schema: type: array items: $ref: "#/components/schemas/Registry" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" post: operationId: createRegistry tags: [Registries] summary: Create registry description: | Adds a new private container registry. The secret is encrypted at rest using AES-256-GCM. Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegistryCreate" responses: "201": description: Registry created. content: application/json: schema: type: object required: [id] properties: id: type: integer "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" /api/registries/{id}: put: operationId: updateRegistry tags: [Registries] summary: Update registry description: | Updates an existing registry. All fields are optional — only provided fields are changed. Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`. parameters: - $ref: "#/components/parameters/idPath" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegistryCreate" responses: "200": description: Registry updated. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "400": description: Validation error. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" delete: operationId: deleteRegistry tags: [Registries] summary: Delete registry description: | Removes a registry and its encrypted credentials. Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Registry deleted. content: application/json: schema: $ref: "#/components/schemas/SuccessBoolean" "400": description: Invalid registry ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalError" /api/registries/{id}/test: post: operationId: testRegistry tags: [Registries] summary: Test registry connection description: | Tests connectivity and authentication against a configured registry. Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`. parameters: - $ref: "#/components/parameters/idPath" responses: "200": description: Test result. content: application/json: schema: type: object properties: success: type: boolean message: type: string "400": description: Invalid registry ID. content: application/json: schema: $ref: "#/components/schemas/Error" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalError" # ── Image Updates ─────────────────────────────────────── /api/image-updates: get: operationId: getImageUpdates tags: [Image Updates] summary: Get image update status description: Returns the update availability status for all stacks. responses: "200": description: Array of update status objects per stack. content: application/json: schema: type: array items: $ref: "#/components/schemas/ImageUpdateStatus" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalError" /api/image-updates/refresh: post: operationId: refreshImageUpdates tags: [Image Updates] summary: Trigger image update check description: | Triggers a background check for newer image versions across all stacks. Rate limited to one refresh per 10 minutes. Requires admin role. responses: "200": description: Refresh started. content: application/json: schema: type: object properties: success: type: boolean message: type: string example: Image update check started in background. "401": $ref: "#/components/responses/Unauthorized" "429": description: Rate limited — wait at least 10 minutes between refreshes. content: application/json: schema: $ref: "#/components/schemas/Error" "500": $ref: "#/components/responses/InternalError" /api/image-updates/status: get: operationId: getImageUpdateCheckStatus tags: [Image Updates] summary: Check if update scan is running description: Returns whether an image update check is currently in progress. responses: "200": description: Check status. content: application/json: schema: type: object required: [checking] properties: checking: type: boolean description: "`true` if a scan is currently running." "401": $ref: "#/components/responses/Unauthorized"