From eae83c997b6d8e204a2a6cc704a6702be7b11921 Mon Sep 17 00:00:00 2001 From: Anso Date: Tue, 31 Mar 2026 16:13:07 -0400 Subject: [PATCH] docs: add OpenAPI 3.1 spec and API Reference tab (#294) Add a complete OpenAPI 3.1 specification covering ~55 public API endpoints across 8 categories (Stacks, Containers, API Tokens, Webhooks, Nodes, Fleet, Scheduled Tasks, Health). Wire it into Mintlify via native OpenAPI rendering with an interactive "Try It" playground and a dedicated API Reference tab. Includes an API overview page documenting authentication, token scopes, node routing, error format, license tier requirements, and WebSocket endpoints. --- CHANGELOG.md | 8 + docs/api-reference/overview.mdx | 171 +++ docs/docs.json | 200 ++- docs/openapi.yaml | 2278 +++++++++++++++++++++++++++++++ 4 files changed, 2614 insertions(+), 43 deletions(-) create mode 100644 docs/api-reference/overview.mdx create mode 100644 docs/openapi.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d109204..2ee57a2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +* **docs:** OpenAPI 3.1 spec covering ~55 public API endpoints across 8 categories (Stacks, Containers, API Tokens, Webhooks, Nodes, Fleet, Scheduled Tasks, Health) +* **docs:** Interactive API Reference tab in Mintlify documentation powered by native OpenAPI rendering +* **docs:** API overview page with authentication guide, token scopes, node routing, error format, and WebSocket examples + ## [0.23.0](https://github.com/AnsoCode/Sencho/compare/v0.22.1...v0.23.0) (2026-03-31) diff --git a/docs/api-reference/overview.mdx b/docs/api-reference/overview.mdx new file mode 100644 index 00000000..33f6f449 --- /dev/null +++ b/docs/api-reference/overview.mdx @@ -0,0 +1,171 @@ +--- +title: API Overview +description: Authenticate, route requests to nodes, and integrate Sencho into your CI/CD pipelines. +--- + +Sencho exposes a REST API for automating stack deployments, managing webhooks, monitoring fleet health, and integrating with CI/CD pipelines. Since Sencho is self-hosted, the API base URL is your own instance. + +## Base URL + +``` +https://your-sencho-instance:3000/api +``` + +Replace with your actual Sencho host and port. All API paths are prefixed with `/api`. + +## Authentication + +Authenticated endpoints require a **Bearer token** in the `Authorization` header. Generate API tokens from **Settings > API Tokens** in the Sencho dashboard. + +```bash +curl -H "Authorization: Bearer YOUR_API_TOKEN" \ + https://your-sencho-instance:3000/api/stacks +``` + + + API Tokens require a Sencho **Admiral** license. Community and Skipper editions do not include this feature. + + +### Token scopes + +Every token is created with one of three permission levels: + +| Scope | Allowed actions | +|-------|----------------| +| **Read Only** | `GET` requests only — view stacks, containers, and fleet status | +| **Deploy Only** | Everything in Read Only, plus stack operations: deploy, down, restart, stop, start, update | +| **Full Admin** | Full stack, container, and node management — all read and write operations | + +Regardless of scope, all API tokens are blocked from managing other tokens, users, licenses, and SSO configuration. + +## Node routing + +Sencho manages multiple nodes (local and remote). To target a specific node, include one of: + +- **Header:** `x-node-id: 1` +- **Query parameter:** `?nodeId=1` + +If omitted, the request targets the default node. + +```bash +# Target node ID 2 +curl -H "Authorization: Bearer TOKEN" \ + -H "x-node-id: 2" \ + https://your-sencho-instance:3000/api/stacks +``` + +## Error format + +All error responses follow the same shape: + +```json +{ + "error": "Human-readable error message", + "code": "MACHINE_READABLE_CODE" +} +``` + +The `code` field is present for specific error types: + +| Code | Meaning | +|------|---------| +| `PRO_REQUIRED` | Endpoint requires a Pro (Skipper or Admiral) license | +| `ADMIRAL_REQUIRED` | Endpoint requires an Admiral license | +| `SCOPE_DENIED` | API token scope does not allow this operation | + +## Rate limiting + +Authentication endpoints (`/api/auth/*`) are rate-limited to **5 requests per 15 minutes** in production. API token-authenticated requests are not rate-limited. + +## License tier requirements + +Some endpoints are gated by license tier: + +| Tier | Gated features | +|------|---------------| +| **Pro (Skipper+)** | Webhooks, Fleet snapshots, Stack rollback | +| **Admiral** | API Tokens, Scheduled Tasks | + +Requests to gated endpoints on a lower tier return `403` with the appropriate error code. + +## WebSocket endpoints + +Sencho also provides real-time streaming via WebSocket connections. These are not part of the OpenAPI spec (OpenAPI does not support WebSocket protocols) but are documented here for completeness. + +### Stack log streaming + +Stream live logs from a stack's containers. + +**URL:** `wss://your-sencho-instance:3000/api/stacks/{stackName}/logs?nodeId={nodeId}` + +**Authentication:** Pass the token as a cookie (`sencho_token`) or Bearer token. For WebSocket connections, authentication is verified during the upgrade handshake. + + + +```javascript Node.js +import WebSocket from "ws"; + +const ws = new WebSocket( + "wss://your-sencho-instance:3000/api/stacks/my-app/logs", + { headers: { Cookie: "sencho_token=YOUR_JWT" } } +); + +ws.on("message", (data) => { + console.log(data.toString()); +}); +``` + +```bash cURL (upgrade check) +curl -i -N \ + -H "Connection: Upgrade" \ + -H "Upgrade: websocket" \ + -H "Cookie: sencho_token=YOUR_JWT" \ + https://your-sencho-instance:3000/api/stacks/my-app/logs +``` + + + +### Container exec + +Open an interactive shell session inside a running container. + +**URL:** `wss://your-sencho-instance:3000/ws` + +**Authentication:** Cookie-based JWT only. API tokens with `read-only` or `deploy-only` scope are blocked. + + + +```javascript Node.js +import WebSocket from "ws"; + +const ws = new WebSocket("wss://your-sencho-instance:3000/ws", { + headers: { Cookie: "sencho_token=YOUR_JWT" }, +}); + +ws.on("open", () => { + // Initiate exec session + ws.send( + JSON.stringify({ + action: "execContainer", + containerId: "abc123...", + }) + ); +}); + +ws.on("message", (data) => { + // Terminal output + process.stdout.write(data.toString()); +}); + +// Send terminal input +ws.send(JSON.stringify({ type: "input", data: "ls -la\n" })); + +// Resize terminal +ws.send(JSON.stringify({ type: "resize", cols: 120, rows: 40 })); +``` + + + + + Container exec requires a Sencho **Admiral Team** license and is blocked for API tokens and node proxy tokens. + diff --git a/docs/docs.json b/docs/docs.json index 6d4ee729..d56ac36b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -67,55 +67,169 @@ } ] }, + "api": { + "auth": { + "method": "bearer", + "name": "Authorization" + } + }, "navigation": { - "groups": [ + "tabs": [ { - "group": "Getting Started", - "pages": [ - "getting-started/introduction", - "getting-started/quickstart", - "getting-started/configuration", - "getting-started/sso-quickstart" + "tab": "Documentation", + "groups": [ + { + "group": "Getting Started", + "pages": [ + "getting-started/introduction", + "getting-started/quickstart", + "getting-started/configuration", + "getting-started/sso-quickstart" + ] + }, + { + "group": "Features", + "pages": [ + "features/overview", + "features/dashboard", + "features/stack-management", + "features/editor", + "features/resources", + "features/app-store", + "features/global-observability", + "features/host-console", + "features/multi-node", + "features/fleet-view", + "features/alerts-notifications", + "features/webhooks", + "features/rbac", + "features/atomic-deployments", + "features/fleet-backups", + "features/audit-log", + "features/api-tokens", + "features/private-registries", + "features/scheduled-operations", + "features/sso", + "features/licensing" + ] + }, + { + "group": "Reference", + "pages": [ + "reference/settings", + "reference/security-advisories" + ] + }, + { + "group": "Operations", + "pages": [ + "operations/troubleshooting", + "operations/backup" + ] + } ] }, { - "group": "Features", + "tab": "API Reference", + "openapi": "openapi.yaml", "pages": [ - "features/overview", - "features/dashboard", - "features/stack-management", - "features/editor", - "features/resources", - "features/app-store", - "features/global-observability", - "features/host-console", - "features/multi-node", - "features/fleet-view", - "features/alerts-notifications", - "features/webhooks", - "features/rbac", - "features/atomic-deployments", - "features/fleet-backups", - "features/audit-log", - "features/api-tokens", - "features/private-registries", - "features/scheduled-operations", - "features/sso", - "features/licensing" - ] - }, - { - "group": "Reference", - "pages": [ - "reference/settings", - "reference/security-advisories" - ] - }, - { - "group": "Operations", - "pages": [ - "operations/troubleshooting", - "operations/backup" + "api-reference/overview", + { + "group": "Health", + "pages": [ + "GET /api/health" + ] + }, + { + "group": "Stacks", + "pages": [ + "GET /api/stacks", + "POST /api/stacks", + "GET /api/stacks/{stackName}", + "PUT /api/stacks/{stackName}", + "DELETE /api/stacks/{stackName}", + "GET /api/stacks/{stackName}/envs", + "GET /api/stacks/{stackName}/env", + "PUT /api/stacks/{stackName}/env", + "GET /api/stacks/{stackName}/containers", + "GET /api/stacks/{stackName}/services", + "POST /api/stacks/{stackName}/deploy", + "POST /api/stacks/{stackName}/down", + "POST /api/stacks/{stackName}/start", + "POST /api/stacks/{stackName}/stop", + "POST /api/stacks/{stackName}/restart", + "POST /api/stacks/{stackName}/update", + "POST /api/stacks/{stackName}/rollback", + "GET /api/stacks/{stackName}/backup" + ] + }, + { + "group": "Containers", + "pages": [ + "GET /api/containers", + "GET /api/containers/{id}/logs", + "POST /api/containers/{id}/start", + "POST /api/containers/{id}/stop", + "POST /api/containers/{id}/restart" + ] + }, + { + "group": "API Tokens", + "pages": [ + "POST /api/api-tokens", + "GET /api/api-tokens", + "DELETE /api/api-tokens/{id}" + ] + }, + { + "group": "Webhooks", + "pages": [ + "GET /api/webhooks", + "POST /api/webhooks", + "PUT /api/webhooks/{id}", + "DELETE /api/webhooks/{id}", + "GET /api/webhooks/{id}/history", + "POST /api/webhooks/{id}/trigger" + ] + }, + { + "group": "Nodes", + "pages": [ + "GET /api/nodes", + "POST /api/nodes", + "GET /api/nodes/{id}", + "PUT /api/nodes/{id}", + "DELETE /api/nodes/{id}", + "POST /api/nodes/{id}/test" + ] + }, + { + "group": "Fleet", + "pages": [ + "GET /api/fleet/overview", + "GET /api/fleet/node/{nodeId}/stacks", + "GET /api/fleet/node/{nodeId}/stacks/{stackName}/containers", + "POST /api/fleet/snapshots", + "GET /api/fleet/snapshots", + "GET /api/fleet/snapshots/{id}", + "POST /api/fleet/snapshots/{id}/restore", + "DELETE /api/fleet/snapshots/{id}" + ] + }, + { + "group": "Scheduled Tasks", + "pages": [ + "GET /api/scheduled-tasks", + "POST /api/scheduled-tasks", + "GET /api/scheduled-tasks/{id}", + "PUT /api/scheduled-tasks/{id}", + "DELETE /api/scheduled-tasks/{id}", + "PATCH /api/scheduled-tasks/{id}/toggle", + "POST /api/scheduled-tasks/{id}/run", + "GET /api/scheduled-tasks/{id}/runs", + "GET /api/scheduled-tasks/{id}/runs/export" + ] + } ] } ] diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 00000000..c80e04c1 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,2278 @@ +openapi: 3.1.0 +info: + title: Sencho API + version: 0.23.0 + 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 (requires Admiral license). + + ``` + 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 a Pro or Admiral license. Requests to gated endpoints + on Community Edition return `403` with `code: "PRO_REQUIRED"` or `code: "ADMIRAL_REQUIRED"`. + contact: + name: Sencho + url: https://sencho.io + license: + name: BSL 1.1 + url: https://github.com/AnsoCode/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: "3000" + +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 (Admiral license required) + - name: Webhooks + description: Configure and trigger deployment webhooks (Pro license required) + - name: Nodes + description: Manage local and remote Sencho nodes + - name: Fleet + description: Multi-node fleet overview and snapshots (Pro license required) + - name: Scheduled Tasks + description: Configure recurring automated operations (Admiral license required) + +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 (URL-encoded if it contains special characters). + schema: + type: string + 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., `PRO_REQUIRED`, `SCOPE_DENIED`). + enum: [PRO_REQUIRED, ADMIRAL_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 + name: + type: string + stack_name: + type: string + action: + type: string + enum: [deploy, restart, stop, start, 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"] + + 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 Sencho Pro." + code: "PRO_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. + + # ── 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. On Pro tier, uses 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 (Pro tier). + + /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`). + On Pro tier, uses 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 Pro license and `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 Admiral license and 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 won't 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. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + $ref: "#/components/responses/Forbidden" + "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. + 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 Admiral license and 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 Admiral license and 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. Requires Pro license. + 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 Pro license and admin role. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [name, stack_name, action] + properties: + name: + type: string + example: GitHub Deploy Hook + stack_name: + type: string + description: Target stack name. + example: my-app + action: + type: string + enum: [deploy, restart, stop, start, pull] + description: Action to execute when the webhook is triggered. + enabled: + type: boolean + default: true + 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 Pro license and admin role. + parameters: + - $ref: "#/components/parameters/idPath" + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + stack_name: + type: string + action: + type: string + enum: [deploy, restart, stop, start, pull] + enabled: + type: boolean + 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 Pro license and 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. Requires Pro license. + 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. + + Compute the signature as: `sha256=` + HMAC-SHA256(raw_request_body, webhook_secret). + security: + - webhookSignature: [] + parameters: + - $ref: "#/components/parameters/idPath" + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + action: + type: string + description: Override the default webhook action. + 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 + "401": + description: Missing or invalid signature. + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + $ref: "#/components/responses/Forbidden" + "404": + description: Webhook not found or disabled. + 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:3000 + 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" + + # ── 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. Requires Pro license. + 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. Requires Pro license. + 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/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 Pro license and 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. Requires Pro license. + 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. Requires Pro license. + 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 Pro license and 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 Pro license and 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 Admiral license and 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 Admiral license and 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 Admiral license and 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 Admiral license and 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 Admiral license and 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 Admiral license and 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 Admiral license and 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 Admiral license and 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"