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"