mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
0daddfde00
* fix: reconcile sticky update indicators with Anatomy preview Sidebar, Updates filter, and Fleet treated retained partial/failed scanner has_update as confirmed. Keep raw state for retention/notifications, project confirmed-only to APIs, show distinct incomplete indicators, and clear sticky rows only after an authoritative-negative preview. Closes #1685 * test: align sidebar truncate E2E with failed-over-retained precedence Purple update indicators are confirmed-only; hasUpdate with a failed check correctly shows the failed trailing icon. * fix: clear confirmed update rows on authoritative-negative preview Address audit SF-1/SF-2/SF-3: observation-watermark clears for older ok+has_update rows (DB + memory gens), Fleet checkability parity with backend not_checkable, and Updates chip confirmed-only regressions. * fix: tombstone equal-generation writers on preview clear Advance the per-stack write generation when clearing at the observation watermark so a scanner reserved before preview cannot recreate the row after an authoritative-negative reconcile. * fix: clear sticky updates with digest and tag preview parity Share detection across scanner and preview, keep GET read-only with POST reconcile, gate Apply to digest and rebuild updates, and invalidate the hub fleet cache on clear. * test: set digestUpdate on auto-update checkImage mocks Scheduler and execute routes now gate Compose on digest drift; fixtures that expect an apply need digestUpdate so they exercise the update path. * fix: clear unused lint errors on sticky update branch Drop unused partial helper and fleet invalidate import; keep the CacheService inflight self-ref as let with an eslint exception so tsc stays green. * fix: use inflight holder for CacheService prefer-const Keep generation-aware ownership without a let self-reference that fights ESLint and tsc.
4200 lines
141 KiB
YAML
4200 lines
141 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: Sencho API
|
|
version: 0.25.3
|
|
description: |
|
|
REST API for Sencho, a self-hosted Docker Compose management dashboard.
|
|
|
|
Sencho exposes a public API for automating stack deployments, managing webhooks,
|
|
monitoring fleet health, and integrating with CI/CD pipelines.
|
|
|
|
## Authentication
|
|
|
|
All authenticated endpoints accept a Bearer token in the `Authorization` header.
|
|
Generate API tokens from **Settings > API Tokens** in the Sencho dashboard (admin role required).
|
|
|
|
```
|
|
Authorization: Bearer YOUR_API_TOKEN
|
|
```
|
|
|
|
## Node Routing
|
|
|
|
Sencho manages multiple nodes (local and remote). To target a specific node,
|
|
include the `x-node-id` header or `?nodeId=` query parameter. If omitted,
|
|
the request targets the default node.
|
|
|
|
## License Tiers
|
|
|
|
Some endpoints require an Admiral license. Requests to gated endpoints
|
|
on Community return `403` with `code: "PAID_REQUIRED"`.
|
|
contact:
|
|
name: Sencho
|
|
url: https://sencho.io
|
|
license:
|
|
name: AGPL-3.0-only
|
|
url: https://github.com/studio-saelix/sencho/blob/main/LICENSE
|
|
|
|
servers:
|
|
- url: "{protocol}://{host}:{port}"
|
|
description: Your Sencho instance
|
|
variables:
|
|
protocol:
|
|
default: https
|
|
enum: [http, https]
|
|
host:
|
|
default: localhost
|
|
port:
|
|
default: "1852"
|
|
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
tags:
|
|
- name: Health
|
|
description: Instance health check
|
|
- name: Stacks
|
|
description: Create, read, update, delete, and operate Docker Compose stacks
|
|
- name: Containers
|
|
description: List and manage running containers
|
|
- name: API Tokens
|
|
description: Manage scoped API tokens (admin role required)
|
|
- name: Webhooks
|
|
description: Configure and trigger deployment webhooks
|
|
- name: Nodes
|
|
description: Manage local and remote Sencho nodes
|
|
- name: Fleet
|
|
description: Multi-node fleet overview and snapshots
|
|
- name: Scheduled Tasks
|
|
description: Configure recurring automated operations (admin role required)
|
|
- name: Registries
|
|
description: Manage private container registry credentials. Docker Hub, GHCR, and custom registries are available on every tier; AWS ECR requires Admiral. These endpoints are only accessible via browser sessions. API tokens receive `SCOPE_DENIED`.
|
|
- name: Image Updates
|
|
description: Check for available container image updates across stacks
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
description: API token generated from Settings > API Tokens. Scopes — `read-only`, `deploy-only`, or `full-admin`.
|
|
webhookSignature:
|
|
type: apiKey
|
|
in: header
|
|
name: X-Webhook-Signature
|
|
description: HMAC-SHA256 signature for webhook trigger requests. Compute as `sha256=HMAC(request_body, webhook_secret)`.
|
|
|
|
parameters:
|
|
stackName:
|
|
name: stackName
|
|
in: path
|
|
required: true
|
|
description: >
|
|
Stack directory name. Must match `^[a-zA-Z0-9_-]+$` (alphanumeric characters,
|
|
hyphens, and underscores only). Returns `400 Invalid stack name` if the name
|
|
contains path separators, dots, spaces, or other special characters.
|
|
schema:
|
|
type: string
|
|
pattern: '^[a-zA-Z0-9_-]+$'
|
|
example: my-stack
|
|
nodeId:
|
|
name: x-node-id
|
|
in: header
|
|
required: false
|
|
description: Target a specific node by ID. If omitted, the default node is used. Can also be passed as `?nodeId=` query parameter.
|
|
schema:
|
|
type: integer
|
|
example: 1
|
|
nodeIdQuery:
|
|
name: nodeId
|
|
in: query
|
|
required: false
|
|
description: Target a specific node by ID (alternative to `x-node-id` header).
|
|
schema:
|
|
type: integer
|
|
example: 1
|
|
idPath:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
description: Numeric resource ID.
|
|
schema:
|
|
type: integer
|
|
|
|
schemas:
|
|
Error:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error:
|
|
type: string
|
|
description: Human-readable error message.
|
|
code:
|
|
type: string
|
|
description: Machine-readable error code (e.g., `PAID_REQUIRED`, `SCOPE_DENIED`).
|
|
enum: [PAID_REQUIRED, SCOPE_DENIED]
|
|
|
|
SuccessMessage:
|
|
type: object
|
|
required: [message]
|
|
properties:
|
|
message:
|
|
type: string
|
|
|
|
SuccessBoolean:
|
|
type: object
|
|
required: [success]
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
example: true
|
|
|
|
UpdatePreviewImage:
|
|
type: object
|
|
required:
|
|
[service, image, current_tag, next_tag, has_update, digest_update, tag_update, semver_bump, check_status]
|
|
properties:
|
|
service: { type: string }
|
|
image: { type: string }
|
|
current_tag: { type: string }
|
|
next_tag: { type: ["string", "null"] }
|
|
has_update: { type: boolean }
|
|
digest_update:
|
|
type: boolean
|
|
description: Same-tag registry content drift; Compose pull can apply without changing the pin.
|
|
tag_update:
|
|
type: boolean
|
|
description: A higher pinned semver exists; advisory until Compose is edited.
|
|
semver_bump:
|
|
type: string
|
|
enum: [none, unknown, patch, minor, major]
|
|
check_status:
|
|
type: string
|
|
enum: [ok, partial, failed, not_checkable]
|
|
|
|
UpdatePreviewSummary:
|
|
type: object
|
|
required:
|
|
[has_update, primary_image, current_tag, next_tag, semver_bump, update_kind, blocked, blocked_reason, has_build_services, rebuild_available, check_status]
|
|
properties:
|
|
has_update: { type: boolean }
|
|
primary_image: { type: ["string", "null"] }
|
|
current_tag: { type: ["string", "null"] }
|
|
next_tag: { type: ["string", "null"] }
|
|
semver_bump:
|
|
type: string
|
|
enum: [none, unknown, patch, minor, major]
|
|
update_kind:
|
|
type: string
|
|
enum: [tag, digest, none]
|
|
blocked: { type: boolean }
|
|
blocked_reason: { type: ["string", "null"] }
|
|
has_build_services: { type: boolean }
|
|
rebuild_available: { type: boolean }
|
|
check_status:
|
|
type: string
|
|
enum: [ok, partial, failed]
|
|
|
|
UpdatePreview:
|
|
type: object
|
|
required: [stack_name, images, build_services, summary, rollback_target, changelog]
|
|
properties:
|
|
stack_name: { type: string }
|
|
images:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/UpdatePreviewImage"
|
|
build_services:
|
|
type: array
|
|
items: { type: string }
|
|
summary:
|
|
$ref: "#/components/schemas/UpdatePreviewSummary"
|
|
rollback_target: { type: ["string", "null"] }
|
|
changelog: { type: ["string", "null"] }
|
|
|
|
LabelSource:
|
|
type: string
|
|
description: Provenance of a label. `unknown` when a container or image could not be inspected.
|
|
enum: [compose, runtime, image, compose-system, unknown]
|
|
|
|
LabelValue:
|
|
type: object
|
|
required: [key, value, source]
|
|
properties:
|
|
key: { type: string }
|
|
value:
|
|
type: string
|
|
description: Redacted to `[redacted]` for secret-like keys unless the caller is an admin and passes `reveal=1`.
|
|
source: { $ref: "#/components/schemas/LabelSource" }
|
|
redacted: { type: boolean }
|
|
|
|
LabelIndexContainerRef:
|
|
type: object
|
|
required: [id, name, stack, service]
|
|
properties:
|
|
id: { type: string }
|
|
name: { type: string }
|
|
stack: { type: string, nullable: true }
|
|
service: { type: string, nullable: true }
|
|
nodeId: { type: integer }
|
|
nodeName: { type: string }
|
|
|
|
LabelIndexRow:
|
|
type: object
|
|
description: One unique key/value/source and every container carrying it.
|
|
required: [key, value, source, containers]
|
|
properties:
|
|
key: { type: string }
|
|
value: { type: string }
|
|
source: { $ref: "#/components/schemas/LabelSource" }
|
|
redacted: { type: boolean }
|
|
containers:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/LabelIndexContainerRef" }
|
|
|
|
ContainerLabelRow:
|
|
type: object
|
|
required: [id, name, stack, service, state, labels]
|
|
properties:
|
|
id: { type: string }
|
|
name: { type: string }
|
|
stack: { type: string, nullable: true }
|
|
service: { type: string, nullable: true }
|
|
state: { type: string }
|
|
labels:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/LabelValue" }
|
|
|
|
StackLabelReplica:
|
|
type: object
|
|
required: [id, name, state, runtimeLabels, onlyInCompose, onlyOnContainer, inBoth, changed]
|
|
properties:
|
|
id: { type: string }
|
|
name: { type: string }
|
|
state: { type: string }
|
|
runtimeLabels:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/LabelValue" }
|
|
onlyInCompose: { type: array, items: { type: string } }
|
|
onlyOnContainer: { type: array, items: { type: string } }
|
|
inBoth: { type: array, items: { type: string } }
|
|
changed:
|
|
type: array
|
|
items: { type: string }
|
|
description: Keys declared in Compose and present at runtime but with a different value.
|
|
inspectFailed:
|
|
type: boolean
|
|
description: Runtime labels could not be read for this replica; reconciliation was skipped.
|
|
|
|
StackServiceLabelRow:
|
|
type: object
|
|
required: [service, declaredLabels, replicas]
|
|
properties:
|
|
service: { type: string }
|
|
declaredLabels:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/LabelValue" }
|
|
replicas:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/StackLabelReplica" }
|
|
|
|
StackLabelInventory:
|
|
type: object
|
|
required: [stackName, renderable, services, partial, generatedAt]
|
|
properties:
|
|
stackName: { type: string }
|
|
renderable:
|
|
type: boolean
|
|
description: False when the Compose model could not be rendered; declared provenance is then unknown.
|
|
partial:
|
|
type: boolean
|
|
description: A replica or its image could not be fully inspected.
|
|
generatedAt: { type: integer }
|
|
services:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/StackServiceLabelRow" }
|
|
|
|
NodeLabelInventory:
|
|
type: object
|
|
required: [nodeId, containers, byLabel, partial, generatedAt]
|
|
properties:
|
|
nodeId: { type: integer }
|
|
partial: { type: boolean }
|
|
generatedAt: { type: integer }
|
|
containers:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/ContainerLabelRow" }
|
|
byLabel:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/LabelIndexRow" }
|
|
|
|
FleetLabelInventory:
|
|
type: object
|
|
required: [nodes, aggregatedByLabel, nodeErrors, generatedAt]
|
|
properties:
|
|
generatedAt: { type: integer }
|
|
nodes:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [nodeId, nodeName, status, inventory, error]
|
|
properties:
|
|
nodeId: { type: integer }
|
|
nodeName: { type: string }
|
|
status: { type: string, enum: [ok, error] }
|
|
inventory:
|
|
nullable: true
|
|
allOf: [{ $ref: "#/components/schemas/NodeLabelInventory" }]
|
|
error: { type: string, nullable: true }
|
|
aggregatedByLabel:
|
|
type: array
|
|
items: { $ref: "#/components/schemas/LabelIndexRow" }
|
|
nodeErrors:
|
|
type: object
|
|
additionalProperties: { type: string }
|
|
description: Map of node id to error for nodes that were unreachable or returned a malformed payload.
|
|
|
|
FailureClassification:
|
|
type: object
|
|
description: Classified cause of a failed deploy or update, with a suggested next step.
|
|
required: [reason, label, suggestion]
|
|
properties:
|
|
reason:
|
|
type: string
|
|
enum:
|
|
- image_pull_failed
|
|
- compose_render_failed
|
|
- env_missing
|
|
- port_conflict
|
|
- bind_path_missing
|
|
- permission_denied
|
|
- container_exited
|
|
- healthcheck_failed
|
|
- dependency_unavailable
|
|
- node_unreachable
|
|
- unknown
|
|
label:
|
|
type: string
|
|
description: Short display headline for the cause.
|
|
suggestion:
|
|
type: string
|
|
description: Suggested next action, one sentence.
|
|
|
|
Container:
|
|
type: object
|
|
properties:
|
|
Id:
|
|
type: string
|
|
description: Docker container ID.
|
|
Names:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Container names (prefixed with `/`).
|
|
Service:
|
|
type: string
|
|
description: Compose service name (if part of a stack).
|
|
State:
|
|
type: string
|
|
description: Container state.
|
|
enum: [running, exited, paused, restarting, dead, created]
|
|
Status:
|
|
type: string
|
|
description: Human-readable status string (e.g., "Up 2 hours").
|
|
Ports:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
PrivatePort:
|
|
type: integer
|
|
PublicPort:
|
|
type: integer
|
|
|
|
Node:
|
|
type: object
|
|
required: [id, name, type, compose_dir, is_default, created_at]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
type:
|
|
type: string
|
|
enum: [local, remote]
|
|
compose_dir:
|
|
type: string
|
|
description: Base directory for Docker Compose stacks on this node.
|
|
is_default:
|
|
type: integer
|
|
enum: [0, 1]
|
|
description: Whether this is the default node (1 = true).
|
|
api_url:
|
|
type: string
|
|
description: Remote node API URL (only for remote nodes).
|
|
api_token:
|
|
type: string
|
|
description: Redacted API token for remote node authentication.
|
|
created_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
|
|
Webhook:
|
|
type: object
|
|
required: [id, name, stack_name, action, secret, enabled, created_at]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
node_id:
|
|
type: integer
|
|
description: Node the webhook executes against.
|
|
name:
|
|
type: string
|
|
stack_name:
|
|
type: string
|
|
action:
|
|
type: string
|
|
enum: [deploy, restart, stop, start, pull, git-pull]
|
|
secret:
|
|
type: string
|
|
description: Masked secret (e.g., `****...****`). Full secret is only returned on creation.
|
|
enabled:
|
|
type: integer
|
|
enum: [0, 1]
|
|
created_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
|
|
WebhookExecution:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
webhook_id:
|
|
type: integer
|
|
triggered_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
status:
|
|
type: string
|
|
response:
|
|
type: string
|
|
error:
|
|
type: ["string", "null"]
|
|
|
|
ApiToken:
|
|
type: object
|
|
required: [id, name, scope, user_id, created_at]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
scope:
|
|
type: string
|
|
enum: [read-only, deploy-only, full-admin]
|
|
user_id:
|
|
type: integer
|
|
created_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
expires_at:
|
|
type: ["integer", "null"]
|
|
description: Unix timestamp (seconds) or null for no expiry.
|
|
revoked_at:
|
|
type: ["integer", "null"]
|
|
last_used_at:
|
|
type: ["integer", "null"]
|
|
|
|
FleetNodeOverview:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
type:
|
|
type: string
|
|
enum: [local, remote]
|
|
status:
|
|
type: string
|
|
enum: [online, offline]
|
|
stats:
|
|
type: ["object", "null"]
|
|
description: Container statistics for the node.
|
|
systemStats:
|
|
type: ["object", "null"]
|
|
description: Host system resource statistics.
|
|
stacks:
|
|
type: ["integer", "null"]
|
|
description: Number of stacks on this node.
|
|
|
|
FleetSnapshot:
|
|
type: object
|
|
required: [id, description, created_by, created_at, node_count, stack_count]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
description:
|
|
type: string
|
|
created_by:
|
|
type: string
|
|
created_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
node_count:
|
|
type: integer
|
|
stack_count:
|
|
type: integer
|
|
skipped_nodes:
|
|
type: string
|
|
description: JSON-stringified array of skipped node names.
|
|
|
|
FleetSnapshotDetail:
|
|
allOf:
|
|
- $ref: "#/components/schemas/FleetSnapshot"
|
|
- type: object
|
|
properties:
|
|
fileDecryptWarnings:
|
|
type: array
|
|
description: Non-sensitive identifiers for snapshot files that could not be decrypted.
|
|
items:
|
|
type: object
|
|
required: [nodeId, nodeName, stackName, filename]
|
|
properties:
|
|
nodeId:
|
|
type: integer
|
|
nodeName:
|
|
type: string
|
|
stackName:
|
|
type: string
|
|
filename:
|
|
type: string
|
|
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:
|
|
oneOf:
|
|
- type: object
|
|
required: [filename, content]
|
|
properties:
|
|
filename:
|
|
type: string
|
|
content:
|
|
type: string
|
|
description: Decrypted file body; may be an empty string.
|
|
- type: object
|
|
required: [filename, unavailable]
|
|
properties:
|
|
filename:
|
|
type: string
|
|
unavailable:
|
|
type: boolean
|
|
enum: [true]
|
|
|
|
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, container]
|
|
target_id:
|
|
type: ["string", "null"]
|
|
description: Stack or container name (when target_type is `stack` or `container`).
|
|
node_id:
|
|
type: ["integer", "null"]
|
|
description: Target node ID (when target_type is `stack` or `container`).
|
|
action:
|
|
type: string
|
|
enum: [restart, snapshot, prune]
|
|
cron_expression:
|
|
type: string
|
|
description: Standard cron expression (5 fields).
|
|
example: "0 3 * * *"
|
|
enabled:
|
|
type: integer
|
|
enum: [0, 1]
|
|
created_by:
|
|
type: string
|
|
created_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
updated_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
last_run_at:
|
|
type: ["integer", "null"]
|
|
next_run_at:
|
|
type: ["integer", "null"]
|
|
last_status:
|
|
type: ["string", "null"]
|
|
last_error:
|
|
type: ["string", "null"]
|
|
prune_targets:
|
|
type: ["string", "null"]
|
|
description: JSON-stringified array of prune targets (containers, images, networks, volumes).
|
|
target_services:
|
|
type: ["string", "null"]
|
|
description: JSON-stringified array of service names to restart.
|
|
prune_label_filter:
|
|
type: ["string", "null"]
|
|
description: Docker label filter for prune operations.
|
|
|
|
ScheduledTaskRun:
|
|
type: object
|
|
required: [id, task_id, started_at, status, triggered_by]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
task_id:
|
|
type: integer
|
|
started_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
completed_at:
|
|
type: ["integer", "null"]
|
|
triggered_by:
|
|
type: string
|
|
enum: [scheduled, manual]
|
|
status:
|
|
type: string
|
|
enum: [pending, running, success, failed]
|
|
output:
|
|
type: ["string", "null"]
|
|
error:
|
|
type: ["string", "null"]
|
|
|
|
Registry:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
example: GitHub Container Registry
|
|
url:
|
|
type: string
|
|
example: ghcr.io
|
|
type:
|
|
type: string
|
|
enum: [dockerhub, ghcr, ecr, custom]
|
|
username:
|
|
type: string
|
|
aws_region:
|
|
type: ["string", "null"]
|
|
description: AWS region (only present for ECR registries).
|
|
created_at:
|
|
type: integer
|
|
description: Unix timestamp (seconds).
|
|
|
|
RegistryCreate:
|
|
type: object
|
|
required: [name, url, type, username, secret]
|
|
properties:
|
|
name:
|
|
type: string
|
|
maxLength: 100
|
|
example: GitHub Container Registry
|
|
url:
|
|
type: string
|
|
maxLength: 500
|
|
example: ghcr.io
|
|
type:
|
|
type: string
|
|
enum: [dockerhub, ghcr, ecr, custom]
|
|
username:
|
|
type: string
|
|
example: my-user
|
|
secret:
|
|
type: string
|
|
description: Password, token, or access key. Write-only — never returned in GET responses.
|
|
aws_region:
|
|
type: string
|
|
description: Required when `type` is `ecr`.
|
|
example: us-east-1
|
|
|
|
RegistryTagList:
|
|
type: object
|
|
required: [tags, nextCursor, registryId, registryName, repository]
|
|
properties:
|
|
tags:
|
|
type: array
|
|
items: { type: string }
|
|
example: [latest, "1.0"]
|
|
nextCursor:
|
|
type: ["string", "null"]
|
|
description: Opaque cursor for the next page, or null when there are no more tags.
|
|
registryId:
|
|
type: integer
|
|
registryName:
|
|
type: string
|
|
repository:
|
|
type: string
|
|
description: Path-safe repository name after Docker Hub library/ normalization when applicable.
|
|
example: library/nginx
|
|
|
|
ImageUpdateStatus:
|
|
type: object
|
|
properties:
|
|
stack_name:
|
|
type: string
|
|
description: Name of the stack.
|
|
has_updates:
|
|
type: integer
|
|
enum: [0, 1]
|
|
description: Whether any images in this stack have newer versions available.
|
|
last_checked:
|
|
type: ["integer", "null"]
|
|
description: Unix timestamp of the last check.
|
|
|
|
ImageUpdateCheckStatus:
|
|
type: object
|
|
properties:
|
|
checking:
|
|
type: boolean
|
|
description: "`true` if a scan is currently running."
|
|
intervalMinutes:
|
|
type: integer
|
|
description: Configured fallback interval in minutes (15-1440).
|
|
lastCheckedAt:
|
|
type: ["integer", "null"]
|
|
description: Unix epoch-ms of the last check start, or null if never checked.
|
|
nextCheckAt:
|
|
type: ["integer", "null"]
|
|
description: Unix epoch-ms of the next scheduled check, or null when not scheduled.
|
|
manualCooldownMinutes:
|
|
type: integer
|
|
description: Fixed cooldown ceiling in minutes for manual refresh.
|
|
manualCooldownRemainingMs:
|
|
type: integer
|
|
description: Live remaining cooldown in milliseconds (0 when refresh is allowed).
|
|
mode:
|
|
type: string
|
|
enum: [interval, cron]
|
|
description: Active scheduling mode.
|
|
cronExpression:
|
|
type: ["string", "null"]
|
|
description: 5-field cron expression when mode is 'cron', null otherwise.
|
|
sidebarIndicators:
|
|
type: boolean
|
|
description: Whether sidebar update-status indicators are enabled. Controlled by the `image_update_sidebar_indicators` global setting. Default is `false`.
|
|
|
|
responses:
|
|
Unauthorized:
|
|
description: Authentication required. Provide a valid Bearer token.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
example:
|
|
error: "Authentication required"
|
|
Forbidden:
|
|
description: Insufficient permissions or license tier.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
example:
|
|
error: "This feature requires a Sencho Admiral license."
|
|
code: "PAID_REQUIRED"
|
|
NotFound:
|
|
description: Resource not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
InternalError:
|
|
description: Internal server error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
paths:
|
|
# ── Health ──────────────────────────────────────────────
|
|
/api/health:
|
|
get:
|
|
operationId: getHealth
|
|
tags: [Health]
|
|
summary: Health check
|
|
description: Returns instance health status and uptime. No authentication required. Useful for uptime monitors and Docker HEALTHCHECK.
|
|
security: []
|
|
responses:
|
|
"200":
|
|
description: Instance is healthy.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status, uptime]
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: ok
|
|
uptime:
|
|
type: number
|
|
description: Process uptime in seconds.
|
|
|
|
/api/meta:
|
|
get:
|
|
operationId: getMeta
|
|
tags: [Health]
|
|
summary: Instance metadata
|
|
description: Returns this Sencho instance's version and list of supported capabilities. No authentication required. Used by nodes to negotiate feature compatibility.
|
|
security: []
|
|
responses:
|
|
"200":
|
|
description: Instance metadata.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [version, capabilities]
|
|
properties:
|
|
version:
|
|
type: string
|
|
description: Sencho version (semver).
|
|
example: "0.32.0"
|
|
capabilities:
|
|
type: array
|
|
description: List of feature capabilities this instance supports.
|
|
items:
|
|
type: string
|
|
example: ["stacks", "containers", "fleet", "auto-updates", "host-console"]
|
|
imagePinKind:
|
|
type: string
|
|
nullable: true
|
|
enum: [floating, semver, digest, unknown]
|
|
description: How the Sencho service image is pinned in the operator's compose file. Omitted when self-update is unavailable or the compose file could not be read.
|
|
updateBlocked:
|
|
type: boolean
|
|
description: True when the image is pinned in a way Fleet cannot repin automatically (digest or unresolved value).
|
|
updateError:
|
|
type: string
|
|
description: Error from the last failed self-update attempt, when present.
|
|
|
|
/api/system/update:
|
|
post:
|
|
operationId: triggerSelfUpdate
|
|
tags: [Health]
|
|
summary: Trigger self-update
|
|
description: |
|
|
Instructs this Sencho instance to pull the update image and recreate its own container.
|
|
An optional release version in the request body drives semver repinning; floating tags are pulled without rewriting the compose file.
|
|
Digest and unresolved pins are rejected with 409 before any pull runs.
|
|
Returns 202 immediately; the actual update happens asynchronously after the response is sent.
|
|
Requires the instance to be deployed via Docker Compose with the Docker socket mounted.
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
targetVersion:
|
|
type: string
|
|
description: Release version to update to (valid semver).
|
|
responses:
|
|
"202":
|
|
description: Update initiated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: "Update initiated. The server will restart shortly."
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"400":
|
|
description: Invalid release version in request body.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"409":
|
|
description: Update blocked because the compose image cannot be repinned automatically.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: string
|
|
code:
|
|
type: string
|
|
"503":
|
|
description: Self-update not available (not running in Docker Compose).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
# ── Stacks ──────────────────────────────────────────────
|
|
/api/stacks:
|
|
get:
|
|
operationId: listStacks
|
|
tags: [Stacks]
|
|
summary: List all stacks
|
|
description: Returns an array of stack directory names on the target node.
|
|
parameters:
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- $ref: "#/components/parameters/nodeIdQuery"
|
|
responses:
|
|
"200":
|
|
description: Array of stack names.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: ["traefik", "portainer", "monitoring"]
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
operationId: createStack
|
|
tags: [Stacks]
|
|
summary: Create a new stack
|
|
description: Creates a new stack directory with a default `compose.yaml` file.
|
|
parameters:
|
|
- $ref: "#/components/parameters/nodeId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [stackName]
|
|
properties:
|
|
stackName:
|
|
type: string
|
|
description: Stack name. Alphanumeric characters, hyphens, and underscores only.
|
|
pattern: "^[a-zA-Z0-9_-]+$"
|
|
example: my-new-stack
|
|
responses:
|
|
"201":
|
|
description: Stack created.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [message, name]
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: Stack created successfully
|
|
name:
|
|
type: string
|
|
example: my-new-stack
|
|
"400":
|
|
description: Invalid stack name.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"409":
|
|
description: Stack already exists.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
example:
|
|
error: Stack already exists
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}:
|
|
get:
|
|
operationId: getStack
|
|
tags: [Stacks]
|
|
summary: Get stack compose file
|
|
description: Returns the raw `compose.yaml` content for the specified stack.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Raw compose.yaml content.
|
|
content:
|
|
text/plain:
|
|
schema:
|
|
type: string
|
|
"400":
|
|
description: Invalid stack name.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
operationId: updateStack
|
|
tags: [Stacks]
|
|
summary: Update stack compose file
|
|
description: Overwrites the `compose.yaml` content for the specified stack. Requires `stack:edit` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [content]
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: Full compose.yaml content.
|
|
responses:
|
|
"200":
|
|
description: Stack saved.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Stack saved successfully
|
|
"400":
|
|
description: Invalid stack name or content.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
operationId: deleteStack
|
|
tags: [Stacks]
|
|
summary: Delete stack
|
|
description: Permanently deletes the stack directory and all its files. Requires `stack:delete` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Stack deleted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Stack deleted successfully
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/envs:
|
|
get:
|
|
operationId: getStackEnvFiles
|
|
tags: [Stacks]
|
|
summary: List env file paths
|
|
description: Returns the list of environment file paths referenced in the stack's compose file (`env_file` directives).
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: List of env file paths.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [envFiles]
|
|
properties:
|
|
envFiles:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: [".env", "config/database.env"]
|
|
"400":
|
|
description: Invalid stack name.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/env-inventory:
|
|
get:
|
|
operationId: getStackEnvInventory
|
|
tags: [Stacks]
|
|
summary: Get environment inventory
|
|
description: >-
|
|
Returns a per-stack inventory of environment variables: each variable's
|
|
source, whether Compose interpolates it or injects it into a container,
|
|
and a status (present, missing, unused, duplicate, or unpersisted), plus
|
|
likely-secret classification. Names only: a variable value is never read
|
|
or returned. When the effective model cannot be rendered, `renderable` is
|
|
false and the inventory is derived from the authored source alone.
|
|
Requires `stack:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: The environment inventory.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [stackName, renderable, items, envFiles, summary]
|
|
properties:
|
|
stackName:
|
|
type: string
|
|
renderable:
|
|
type: boolean
|
|
description: False when the effective model could not be rendered; the inventory is then partial.
|
|
items:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [key, sources, usedForInterpolation, injectedIntoService, required, hasDefault, likelySecret, status]
|
|
properties:
|
|
key:
|
|
type: string
|
|
sources:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [compose-inline, env-file, dotenv, process-env, compose-ref]
|
|
usedForInterpolation:
|
|
type: boolean
|
|
injectedIntoService:
|
|
type: boolean
|
|
required:
|
|
type: boolean
|
|
hasDefault:
|
|
type: boolean
|
|
likelySecret:
|
|
type: boolean
|
|
status:
|
|
type: string
|
|
enum: [present, missing, unused, duplicate, unpersisted]
|
|
envFiles:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
rawPaths:
|
|
type: array
|
|
items:
|
|
type: string
|
|
existence:
|
|
type: string
|
|
enum: [present, missing, unverifiable]
|
|
required:
|
|
type: boolean
|
|
isInterpolationSource:
|
|
type: boolean
|
|
isInjectionSource:
|
|
type: boolean
|
|
declaringServices:
|
|
type: array
|
|
items:
|
|
type: string
|
|
summary:
|
|
type: object
|
|
properties:
|
|
total: { type: integer }
|
|
missing: { type: integer }
|
|
unused: { type: integer }
|
|
duplicate: { type: integer }
|
|
unpersisted: { type: integer }
|
|
likelySecret: { type: integer }
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: Stack not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/label-inventory:
|
|
get:
|
|
operationId: getStackLabelInventory
|
|
tags: [Stacks]
|
|
summary: Get Docker label inventory for a stack
|
|
description: >-
|
|
Returns declared Compose labels and runtime container labels per service,
|
|
with reconciliation hints (only in Compose, only on running container,
|
|
present in both, or value changed when the values differ) and provenance
|
|
for each runtime label (compose, image, runtime, compose-system, or unknown
|
|
when a container or image cannot be inspected). Secret-like label values are
|
|
redacted unless the caller is an admin and passes `reveal=1`. Requires
|
|
`stack:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: reveal
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ['1', 'true']
|
|
responses:
|
|
"200":
|
|
description: Stack label inventory.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StackLabelInventory"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: Stack not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/system/container-labels:
|
|
get:
|
|
operationId: getNodeContainerLabels
|
|
tags: [Fleet]
|
|
summary: Get the Docker label inventory for the active node
|
|
description: >-
|
|
Returns every container on the node with its labels and provenance
|
|
(compose, image, runtime, compose-system, or unknown), plus an inverted
|
|
`byLabel` index. Marks `partial` when a container or image inspect fails.
|
|
Secret-like values are redacted unless the caller is an admin and passes
|
|
`reveal=1`. Requires `node:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: reveal
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ['1', 'true']
|
|
responses:
|
|
"200":
|
|
description: Node container label inventory.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NodeLabelInventory"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/fleet/container-labels:
|
|
get:
|
|
operationId: getFleetContainerLabels
|
|
tags: [Fleet]
|
|
summary: Get the Docker label inventory aggregated across the fleet
|
|
description: >-
|
|
Fans out to every node's container label inventory and aggregates the
|
|
inverted index by key, value, and source. Unreachable or malformed nodes
|
|
degrade into `nodeErrors` rather than failing the whole request. Secret-like
|
|
values are redacted unless the caller is an admin and passes `reveal=1`.
|
|
Requires `node:read` permission.
|
|
parameters:
|
|
- name: reveal
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
enum: ['1', 'true']
|
|
responses:
|
|
"200":
|
|
description: Fleet-wide container label inventory.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/FleetLabelInventory"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/env:
|
|
get:
|
|
operationId: getStackEnv
|
|
tags: [Stacks]
|
|
summary: Get env file content
|
|
description: Returns the content of an environment file. Defaults to `.env` if no `file` query parameter is specified.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: file
|
|
in: query
|
|
required: false
|
|
description: Path to a specific env file (must be in the resolved env files list).
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Raw env file content (KEY=VALUE format).
|
|
content:
|
|
text/plain:
|
|
schema:
|
|
type: string
|
|
"400":
|
|
description: Invalid stack name or disallowed file path.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"404":
|
|
description: Env file not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
example:
|
|
error: Env file not found
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
operationId: updateStackEnv
|
|
tags: [Stacks]
|
|
summary: Update env file
|
|
description: Overwrites an environment file's content. Requires `stack:edit` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: file
|
|
in: query
|
|
required: false
|
|
description: Path to a specific env file to update.
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [content]
|
|
properties:
|
|
content:
|
|
type: string
|
|
description: Full env file content (KEY=VALUE format).
|
|
responses:
|
|
"200":
|
|
description: Env file saved.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Env file saved successfully
|
|
"400":
|
|
description: Invalid input.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/containers:
|
|
get:
|
|
operationId: getStackContainers
|
|
tags: [Stacks]
|
|
summary: List stack containers
|
|
description: Returns all containers belonging to the specified stack.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Array of container objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Container"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/services:
|
|
get:
|
|
operationId: getStackServices
|
|
tags: [Stacks]
|
|
summary: List stack services
|
|
description: Returns the service names defined in the stack's compose file.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Array of service names.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: ["web", "db", "redis"]
|
|
"400":
|
|
description: Invalid stack name.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/deploy:
|
|
post:
|
|
operationId: deployStack
|
|
tags: [Stacks]
|
|
summary: Deploy stack
|
|
description: |
|
|
Runs `docker compose up -d` for the stack using atomic deployment
|
|
with automatic rollback on failure. Requires `stack:deploy` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Stack deployed successfully.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [message]
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: Deployed successfully
|
|
healthGateId:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Id of the post-deploy health gate observation started for
|
|
this deploy, for use with the health-gate endpoint. Null
|
|
when the health gate is disabled on the node.
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: |
|
|
Deploy blocked before Compose ran. Common codes include a stack
|
|
operation already in progress, a scan-policy block, or missing
|
|
external networks (`code: missing_external_networks`).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: string
|
|
code:
|
|
type: string
|
|
example: missing_external_networks
|
|
kind:
|
|
type: string
|
|
enum: [prompt, unsupported, unavailable, create_failed]
|
|
networks:
|
|
type: array
|
|
items:
|
|
type: object
|
|
createdNames:
|
|
type: array
|
|
items:
|
|
type: string
|
|
remainingNames:
|
|
type: array
|
|
items:
|
|
type: string
|
|
"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.
|
|
failure:
|
|
$ref: "#/components/schemas/FailureClassification"
|
|
"503":
|
|
description: |
|
|
Deploy blocked because Sencho could not render the Compose model or
|
|
read Docker networking state to verify required external networks.
|
|
|
|
/api/stacks/{stackName}/missing-external-networks:
|
|
get:
|
|
operationId: getMissingExternalNetworks
|
|
tags: [Stacks]
|
|
summary: List missing external networks for a stack
|
|
description: |
|
|
Returns whether declared `external: true` networks are missing on the
|
|
target node, whether automatic creation is enabled, and per-network
|
|
safety metadata. Requires `stack:read`. Used by interactive deploy
|
|
preflight when the node advertises `guided-external-network-preflight`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Missing-external-network envelope.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status, autoCreateEnabled, stackName, networks, declaredExternalCount]
|
|
properties:
|
|
status:
|
|
type: string
|
|
enum: [ok, render_unavailable, runtime_unavailable]
|
|
autoCreateEnabled:
|
|
type: boolean
|
|
stackName:
|
|
type: string
|
|
declaredExternalCount:
|
|
type: integer
|
|
networks:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [name, keys, declarations, safe, unsupportedFeatures, creationSpec]
|
|
properties:
|
|
name:
|
|
type: string
|
|
keys:
|
|
type: array
|
|
items:
|
|
type: string
|
|
declarations:
|
|
type: array
|
|
items:
|
|
type: object
|
|
safe:
|
|
type: boolean
|
|
blockReason:
|
|
type: string
|
|
unsupportedFeatures:
|
|
type: array
|
|
items:
|
|
type: string
|
|
creationSpec:
|
|
type: object
|
|
nullable: true
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: Stack not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/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. The stack definition remains on disk. Requires `stack:deploy` permission. Pass `removeVolumes=true` to also remove compose volumes when the node advertises the `stack-down-remove-volumes` capability.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: removeVolumes
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: boolean
|
|
description: When `true`, runs `docker compose down --volumes`. Only honored when the target node advertises `stack-down-remove-volumes`.
|
|
responses:
|
|
"200":
|
|
description: Command started.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status]
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: Command started
|
|
"400":
|
|
description: Volume removal requested on a node that does not support it.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/start:
|
|
post:
|
|
operationId: startStack
|
|
tags: [Stacks]
|
|
summary: Start stack containers
|
|
description: Starts all stopped containers in the stack via the Docker Engine API. Requires `stack:deploy` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Containers started.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: No containers found for this stack.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/stop:
|
|
post:
|
|
operationId: stopStack
|
|
tags: [Stacks]
|
|
summary: Stop stack containers
|
|
description: Stops all running containers in the stack via the Docker Engine API. Requires `stack:deploy` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Containers stopped.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: No containers found for this stack.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/restart:
|
|
post:
|
|
operationId: restartStack
|
|
tags: [Stacks]
|
|
summary: Restart stack containers
|
|
description: Restarts all containers in the stack via the Docker Engine API. Requires `stack:deploy` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Containers restarted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: No containers found for this stack.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/update:
|
|
post:
|
|
operationId: updateStackImages
|
|
tags: [Stacks]
|
|
summary: Pull and recreate stack
|
|
description: |
|
|
Pulls latest images and recreates containers (`docker compose pull && up -d`)
|
|
using atomic update with automatic rollback on failure.
|
|
Requires `stack:deploy` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Update completed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status]
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: Update completed
|
|
healthGateId:
|
|
type: string
|
|
nullable: true
|
|
description: |
|
|
Id of the post-update health gate observation started for
|
|
this update, for use with the health-gate endpoint. Null
|
|
when the health gate is disabled on the node.
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
description: Update failed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error:
|
|
type: string
|
|
rolledBack:
|
|
type: boolean
|
|
failure:
|
|
$ref: "#/components/schemas/FailureClassification"
|
|
|
|
/api/stacks/{stackName}/rollback:
|
|
post:
|
|
operationId: rollbackStack
|
|
tags: [Stacks]
|
|
summary: Rollback stack
|
|
description: Restores the stack to its previous deployment state. Requires `stack:deploy` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Stack rolled back.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: 'Stack rolled back: compose and env files restored.'
|
|
"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"
|
|
|
|
/api/stacks/{stackName}/update-preview:
|
|
get:
|
|
operationId: getStackUpdatePreview
|
|
tags: [Stacks]
|
|
summary: Compute image update preview (read-only)
|
|
description: |
|
|
Computes the current registry update preview for the stack without
|
|
mutating persisted scanner state. Use POST when the client should
|
|
reconcile sticky update indicators after an authoritative-negative
|
|
result. Requires `stack:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Update preview.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/UpdatePreview"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
operationId: reconcileStackUpdatePreview
|
|
tags: [Stacks]
|
|
summary: Compute update preview and reconcile sticky state
|
|
description: |
|
|
Computes the same preview as GET. When every image reports
|
|
`check_status: ok` and `has_update` is false (mixed `ok` +
|
|
`not_checkable` does not clear), clears sticky confirmed update rows
|
|
for the stack and sets `reconciled: true`. Requires `stack:read`
|
|
permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Update preview, with reconcile outcome.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/UpdatePreview"
|
|
- type: object
|
|
required: [reconciled]
|
|
properties:
|
|
reconciled:
|
|
type: boolean
|
|
description: |
|
|
True when sticky update rows were cleared after an
|
|
authoritative-negative preview.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/update-readiness:
|
|
get:
|
|
operationId: getStackUpdateReadiness
|
|
tags: [Stacks]
|
|
summary: Check update readiness
|
|
description: |
|
|
Computes an advisory readiness verdict for updating the stack right
|
|
now, from the stored preflight result, open drift findings, live
|
|
container health, the pending image change, the rollback backup slot,
|
|
and node disk headroom. Advisory only: no verdict blocks an update.
|
|
Requires `stack:read` permission. Pass `service` to scope the verdict
|
|
to one Compose service on a multi-service stack.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: service
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: |
|
|
When set, readiness is scoped to this Compose service (multi-service
|
|
stacks only). Stack-wide guardrails still apply; sibling and
|
|
dependency signals become advisory. Single-service stacks reject
|
|
this parameter with 400.
|
|
responses:
|
|
"200":
|
|
description: Readiness report.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [stack, computedAt, verdict, signals]
|
|
properties:
|
|
stack:
|
|
type: string
|
|
computedAt:
|
|
type: integer
|
|
description: Unix timestamp (ms) the report was computed.
|
|
verdict:
|
|
type: string
|
|
enum: [ready, ready_with_warnings, review_required, blocked, unknown]
|
|
signals:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [id, status, title, detail, affectsVerdict]
|
|
properties:
|
|
id:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [ok, warning, attention, blocked, unknown]
|
|
title:
|
|
type: string
|
|
detail:
|
|
type: string
|
|
affectsVerdict:
|
|
type: boolean
|
|
"400":
|
|
description: Invalid service scope.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/effective-services:
|
|
get:
|
|
operationId: getStackEffectiveServices
|
|
tags: [Stacks]
|
|
summary: List effective Compose services
|
|
description: |
|
|
Returns the fail-closed effective service model for the stack (declared
|
|
image, build presence, expected replicas, depends_on, healthcheck).
|
|
Requires `stack:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Effective model result.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [renderable]
|
|
properties:
|
|
renderable:
|
|
type: boolean
|
|
services:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [name, declaredImage, hasBuild, expectedReplicas, dependsOn, hasHealthcheck]
|
|
properties:
|
|
name:
|
|
type: string
|
|
declaredImage:
|
|
type: string
|
|
nullable: true
|
|
hasBuild:
|
|
type: boolean
|
|
expectedReplicas:
|
|
type: integer
|
|
dependsOn:
|
|
type: array
|
|
items:
|
|
type: string
|
|
hasHealthcheck:
|
|
type: boolean
|
|
code:
|
|
type: string
|
|
error:
|
|
type: string
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/services/{serviceName}/update:
|
|
post:
|
|
operationId: updateStackService
|
|
tags: [Stacks]
|
|
summary: Update or rebuild one Compose service
|
|
description: |
|
|
Manually pulls or builds one declared service, then recreates only that
|
|
service with `--no-deps --force-recreate`. Multi-service stacks only.
|
|
Requires `stack:deploy`. Automation paths continue to update the full stack.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- name: serviceName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Service update completed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [serviceName, healthGateId, observing, recoveryAvailable]
|
|
properties:
|
|
serviceName:
|
|
type: string
|
|
healthGateId:
|
|
type: string
|
|
nullable: true
|
|
observing:
|
|
type: boolean
|
|
recoveryId:
|
|
type: string
|
|
nullable: true
|
|
recoveryAvailable:
|
|
type: boolean
|
|
recheckWarning:
|
|
type: string
|
|
"400":
|
|
description: Service update rejected (single-service stack, missing service, or unavailable capability).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
description: Service update failed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/api/stacks/{stackName}/services/{serviceName}/recovery:
|
|
get:
|
|
operationId: getStackServiceRecovery
|
|
tags: [Stacks]
|
|
summary: Latest active service recovery snapshot
|
|
description: |
|
|
Returns the newest active, unexpired recovery row for one service, or
|
|
`recovery: null` when none is available. Used so Restore stays
|
|
discoverable when Deploy Progress is disabled or dismissed.
|
|
Requires `stack:deploy`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- name: serviceName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Recovery lookup completed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [recovery]
|
|
properties:
|
|
recovery:
|
|
oneOf:
|
|
- type: "null"
|
|
- type: object
|
|
required: [id, status, expiresAt, createdAt]
|
|
properties:
|
|
id:
|
|
type: string
|
|
status:
|
|
type: string
|
|
healthGateId:
|
|
type: string
|
|
nullable: true
|
|
expiresAt:
|
|
type: integer
|
|
createdAt:
|
|
type: integer
|
|
majorityImageId:
|
|
type: string
|
|
declaredImageRef:
|
|
type: string
|
|
"400":
|
|
description: Capability unavailable.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/stacks/{stackName}/services/{serviceName}/restore:
|
|
post:
|
|
operationId: restoreStackService
|
|
tags: [Stacks]
|
|
summary: Restore one Compose service from a recovery snapshot
|
|
description: |
|
|
Retags the captured majority image onto the declared tag and recreates
|
|
only that service. Body must include `recoveryId` from a prior service
|
|
update. Requires `stack:deploy`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- name: serviceName
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- $ref: "#/components/parameters/nodeId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [recoveryId]
|
|
properties:
|
|
recoveryId:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Service restore completed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [serviceName, healthGateId, observing, recoveryAvailable]
|
|
properties:
|
|
serviceName:
|
|
type: string
|
|
healthGateId:
|
|
type: string
|
|
nullable: true
|
|
observing:
|
|
type: boolean
|
|
recoveryId:
|
|
type: string
|
|
nullable: true
|
|
recoveryAvailable:
|
|
type: boolean
|
|
"400":
|
|
description: Restore rejected (missing recoveryId or invalid binding).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
description: Service restore failed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/api/stacks/{stackName}/rollback-readiness:
|
|
get:
|
|
operationId: getStackRollbackReadiness
|
|
tags: [Stacks]
|
|
summary: Check rollback readiness
|
|
description: |
|
|
Reports what a rollback of this stack can actually restore: the backed
|
|
up compose file, env coverage (variable names only, never values), the
|
|
known previous image tag, and an explicit disclosure that volume and
|
|
bind-mounted data are not covered by file backups.
|
|
Requires `stack:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Rollback readiness report.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [stack, computedAt, overall, items]
|
|
properties:
|
|
stack:
|
|
type: string
|
|
computedAt:
|
|
type: integer
|
|
overall:
|
|
type: string
|
|
enum: [ready, partial, not_ready]
|
|
items:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [id, state, label, detail]
|
|
properties:
|
|
id:
|
|
type: string
|
|
state:
|
|
type: string
|
|
enum: [ready, missing, unknown, not_covered]
|
|
label:
|
|
type: string
|
|
detail:
|
|
type: string
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/stacks/{stackName}/health-gate:
|
|
get:
|
|
operationId: getStackHealthGate
|
|
tags: [Stacks]
|
|
summary: Get post-update health gate result
|
|
description: |
|
|
Returns a post-deploy health gate observation for the stack: the run
|
|
named by `gateId` (as returned in a deploy or update response), or the
|
|
most recent run when omitted. Status `never-run` means no observation
|
|
exists. Requires `stack:read` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/stackName"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- name: gateId
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
description: A specific gate run id to read instead of the latest.
|
|
responses:
|
|
"200":
|
|
description: Health gate report.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [stack, status]
|
|
properties:
|
|
stack:
|
|
type: string
|
|
id:
|
|
type: string
|
|
nullable: true
|
|
status:
|
|
type: string
|
|
enum: [observing, passed, failed, unknown, never-run]
|
|
trigger:
|
|
type: string
|
|
nullable: true
|
|
enum: [update, deploy, service_update, service_restore, null]
|
|
reason:
|
|
type: string
|
|
nullable: true
|
|
windowSeconds:
|
|
type: integer
|
|
nullable: true
|
|
startedAt:
|
|
type: integer
|
|
nullable: true
|
|
endedAt:
|
|
type: integer
|
|
nullable: true
|
|
containers:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [name, state, restarts]
|
|
properties:
|
|
name:
|
|
type: string
|
|
state:
|
|
type: string
|
|
description: Docker container state, or `missing` when it vanished mid-observation.
|
|
health:
|
|
type: string
|
|
nullable: true
|
|
restarts:
|
|
type: integer
|
|
service:
|
|
type: string
|
|
nullable: true
|
|
role:
|
|
type: string
|
|
nullable: true
|
|
enum: [primary, collateral, null]
|
|
targetScope:
|
|
type: string
|
|
enum: [stack, service]
|
|
description: Defaults to stack for legacy runs.
|
|
serviceName:
|
|
type: string
|
|
nullable: true
|
|
failureSource:
|
|
type: string
|
|
nullable: true
|
|
enum: [primary, collateral, null]
|
|
description: Set on failed service-scoped runs; null for stack and successful runs.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
|
|
# ── Containers ──────────────────────────────────────────
|
|
/api/containers:
|
|
get:
|
|
operationId: listContainers
|
|
tags: [Containers]
|
|
summary: List all containers
|
|
description: Returns all running containers on the target node.
|
|
parameters:
|
|
- $ref: "#/components/parameters/nodeId"
|
|
- $ref: "#/components/parameters/nodeIdQuery"
|
|
responses:
|
|
"200":
|
|
description: Array of container objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Container"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/containers/{id}/logs:
|
|
get:
|
|
operationId: streamContainerLogs
|
|
tags: [Containers]
|
|
summary: Stream container logs
|
|
description: |
|
|
Streams container logs as Server-Sent Events (SSE). The connection remains
|
|
open and pushes new log lines as `data:` events in real time.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: SSE log stream.
|
|
content:
|
|
text/event-stream:
|
|
schema:
|
|
type: string
|
|
description: Each event is a `data:` line containing a JSON-encoded log string.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/containers/{id}/start:
|
|
post:
|
|
operationId: startContainer
|
|
tags: [Containers]
|
|
summary: Start container
|
|
description: Starts a stopped container. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Container started.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Container started
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/containers/{id}/stop:
|
|
post:
|
|
operationId: stopContainer
|
|
tags: [Containers]
|
|
summary: Stop container
|
|
description: Stops a running container. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Container stopped.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Container stopped
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/containers/{id}/restart:
|
|
post:
|
|
operationId: restartContainer
|
|
tags: [Containers]
|
|
summary: Restart container
|
|
description: Restarts a container. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
- $ref: "#/components/parameters/nodeId"
|
|
responses:
|
|
"200":
|
|
description: Container restarted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Container restarted
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ── API Tokens ──────────────────────────────────────────
|
|
/api/api-tokens:
|
|
post:
|
|
operationId: createApiToken
|
|
tags: [API Tokens]
|
|
summary: Create API token
|
|
description: |
|
|
Generates a new scoped API token. The full token is only returned in the creation response
|
|
and cannot be retrieved again. Requires admin role.
|
|
|
|
**Note:** API tokens cannot create other API tokens.
|
|
responses:
|
|
"201":
|
|
description: Token created. The `token` field contains the full JWT. Save it now; it will not be shown again.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [id, token]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
token:
|
|
type: string
|
|
description: Full JWT token. Store securely — this is the only time it's returned.
|
|
"400":
|
|
description: Validation error (missing/invalid fields, or maximum of 25 active tokens reached).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: An active token with this name already exists.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name, scope]
|
|
properties:
|
|
name:
|
|
type: string
|
|
description: Human-readable token name. Must be unique among the user's active tokens.
|
|
maxLength: 100
|
|
example: CI/CD Deploy Token
|
|
scope:
|
|
type: string
|
|
description: |
|
|
Permission scope:
|
|
- `read-only` — GET requests only
|
|
- `deploy-only` — Read + stack deploy/stop/restart operations
|
|
- `full-admin` — All operations (except license, user, and token management)
|
|
enum: [read-only, deploy-only, full-admin]
|
|
expires_in:
|
|
type: ["integer", "null"]
|
|
description: Token lifetime in days. Use `null` for no expiry.
|
|
enum: [30, 60, 90, 365, null]
|
|
example: 90
|
|
get:
|
|
operationId: listApiTokens
|
|
tags: [API Tokens]
|
|
summary: List API tokens
|
|
description: |
|
|
Returns all API tokens for the current user. Token hashes are never exposed.
|
|
Requires admin role.
|
|
|
|
**Note:** API tokens cannot list other API tokens.
|
|
responses:
|
|
"200":
|
|
description: Array of token metadata.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ApiToken"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/api-tokens/{id}:
|
|
delete:
|
|
operationId: revokeApiToken
|
|
tags: [API Tokens]
|
|
summary: Revoke API token
|
|
description: |
|
|
Permanently revokes an API token. Users can only revoke their own tokens.
|
|
Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Token revoked.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"400":
|
|
description: Invalid token ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ── Webhooks ────────────────────────────────────────────
|
|
/api/webhooks:
|
|
get:
|
|
operationId: listWebhooks
|
|
tags: [Webhooks]
|
|
summary: List webhooks
|
|
description: Returns all configured webhooks with masked secrets.
|
|
responses:
|
|
"200":
|
|
description: Array of webhook objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Webhook"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
operationId: createWebhook
|
|
tags: [Webhooks]
|
|
summary: Create webhook
|
|
description: |
|
|
Creates a new webhook for a stack. The webhook secret is auto-generated and only
|
|
returned in the creation response. Requires admin role.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name, stack_name, action]
|
|
properties:
|
|
name:
|
|
type: string
|
|
maxLength: 100
|
|
example: GitHub Deploy Hook
|
|
stack_name:
|
|
type: string
|
|
description: Target stack name.
|
|
example: my-app
|
|
action:
|
|
type: string
|
|
enum: [deploy, restart, stop, start, pull, git-pull]
|
|
description: Action to execute when the webhook is triggered. `git-pull` requires a Git source attached to the stack.
|
|
enabled:
|
|
type: boolean
|
|
default: true
|
|
node_id:
|
|
type: integer
|
|
description: Node the webhook executes against. Defaults to the request's resolved node or the default node.
|
|
responses:
|
|
"201":
|
|
description: Webhook created. The `secret` field is the HMAC signing key; save it now.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [id, secret]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
secret:
|
|
type: string
|
|
description: HMAC-SHA256 signing secret. Store securely; this is the only time it's returned in full.
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/webhooks/{id}:
|
|
put:
|
|
operationId: updateWebhook
|
|
tags: [Webhooks]
|
|
summary: Update webhook
|
|
description: Updates webhook configuration. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
maxLength: 100
|
|
stack_name:
|
|
type: string
|
|
action:
|
|
type: string
|
|
enum: [deploy, restart, stop, start, pull, git-pull]
|
|
enabled:
|
|
type: boolean
|
|
node_id:
|
|
type: integer
|
|
description: Retarget the webhook to a different node.
|
|
responses:
|
|
"200":
|
|
description: Webhook updated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
operationId: deleteWebhook
|
|
tags: [Webhooks]
|
|
summary: Delete webhook
|
|
description: Permanently deletes a webhook. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Webhook deleted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/webhooks/{id}/history:
|
|
get:
|
|
operationId: getWebhookHistory
|
|
tags: [Webhooks]
|
|
summary: Get webhook execution history
|
|
description: Returns the execution log for a webhook.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Array of execution records.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/WebhookExecution"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/webhooks/{id}/trigger:
|
|
post:
|
|
operationId: triggerWebhook
|
|
tags: [Webhooks]
|
|
summary: Trigger webhook
|
|
description: |
|
|
Externally triggers a webhook action. This endpoint is public but requires a valid
|
|
HMAC-SHA256 signature in the `X-Webhook-Signature` header. Sign the exact raw bytes
|
|
of the request body; an empty body is rejected.
|
|
|
|
Compute the signature as: `sha256=` + HMAC-SHA256(raw_request_body, webhook_secret).
|
|
|
|
Every unauthenticated rejection (unknown id, disabled webhook, non-paid licence,
|
|
missing or invalid signature, empty body) returns the same `404` response so callers
|
|
cannot enumerate webhook ids or fingerprint the instance's licence tier.
|
|
security:
|
|
- webhookSignature: []
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
action:
|
|
type: string
|
|
enum: [deploy, restart, stop, start, pull, git-pull]
|
|
description: Override the default webhook action. Must be one of the allowed actions.
|
|
responses:
|
|
"202":
|
|
description: Webhook accepted and action queued.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [message, action]
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: Webhook accepted
|
|
action:
|
|
type: string
|
|
example: deploy
|
|
"400":
|
|
description: Authentication succeeded but the body's `action` override was not in the allowlist.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"404":
|
|
description: Authentication failed. The webhook is unknown, disabled, the licence is not paid, the signature header is missing, the body was empty, or the signature did not match. Sencho returns the same response for every unauthenticated case.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"429":
|
|
description: Rate limit exceeded. The trigger endpoint allows 500 requests per minute per source IP; CI/CD callers behind shared NAT may share that budget.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ── Nodes ───────────────────────────────────────────────
|
|
/api/nodes:
|
|
get:
|
|
operationId: listNodes
|
|
tags: [Nodes]
|
|
summary: List all nodes
|
|
description: Returns all registered nodes (local and remote).
|
|
responses:
|
|
"200":
|
|
description: Array of node objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Node"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
operationId: createNode
|
|
tags: [Nodes]
|
|
summary: Register a new node
|
|
description: |
|
|
Adds a new local or remote node. Only one local node is allowed per instance;
|
|
attempting to create a second local node returns 409. Remote nodes require
|
|
an API URL pointing to another Sencho instance and an API token for
|
|
authentication.
|
|
Requires `node:manage` permission. Node type is immutable after creation.
|
|
|
|
**Note:** API tokens cannot manage nodes.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name, type]
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: production-server
|
|
type:
|
|
type: string
|
|
enum: [local, remote]
|
|
compose_dir:
|
|
type: string
|
|
description: Base directory for Compose stacks (defaults to `/app/compose`).
|
|
is_default:
|
|
type: boolean
|
|
description: Set as the default node.
|
|
api_url:
|
|
type: string
|
|
description: Remote Sencho instance URL (required for remote nodes).
|
|
example: https://sencho.example.com:1852
|
|
api_token:
|
|
type: string
|
|
description: API token for authenticating with the remote Sencho instance.
|
|
responses:
|
|
"200":
|
|
description: Node created.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [success, id]
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
id:
|
|
type: integer
|
|
description: New node ID.
|
|
warning:
|
|
type: string
|
|
description: Warning if the remote URL uses plain HTTP.
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"409":
|
|
description: Node name already exists, or a local node 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. Node type is immutable after creation. 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. The last local node cannot be deleted. Requires `node:manage` permission.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Node deleted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"400":
|
|
description: Cannot delete the only local node (or the default node).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/nodes/{id}/test:
|
|
post:
|
|
operationId: testNodeConnection
|
|
tags: [Nodes]
|
|
summary: Test node connection
|
|
description: Tests connectivity to a node. For remote nodes, pings the remote Sencho instance's health endpoint.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Connection test result.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [success]
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
error:
|
|
type: string
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/nodes/{id}/meta:
|
|
get:
|
|
operationId: getNodeMeta
|
|
tags: [Nodes]
|
|
summary: Get node capabilities
|
|
description: "Returns the version and supported capabilities for a specific node. For local nodes, returns this instance's own metadata. For remote nodes, relays the response from the remote instance's `/api/meta` endpoint. Returns `{ version: null, capabilities: [] }` if the remote node is offline or doesn't support the meta endpoint."
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Node capability metadata.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [version, capabilities]
|
|
properties:
|
|
version:
|
|
type: string
|
|
nullable: true
|
|
description: Sencho version of the node, or null if unavailable.
|
|
example: "0.31.0"
|
|
capabilities:
|
|
type: array
|
|
description: List of feature capabilities the node supports. Empty array if unavailable.
|
|
items:
|
|
type: string
|
|
example: ["stacks", "containers", "fleet"]
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
|
|
# ── Fleet ───────────────────────────────────────────────
|
|
/api/fleet/overview:
|
|
get:
|
|
operationId: getFleetOverview
|
|
tags: [Fleet]
|
|
summary: Fleet overview
|
|
description: Returns an aggregated overview of all nodes including status, container stats, and stack counts.
|
|
responses:
|
|
"200":
|
|
description: Array of fleet node overviews.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/FleetNodeOverview"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/fleet/node/{nodeId}/stacks:
|
|
get:
|
|
operationId: getFleetNodeStacks
|
|
tags: [Fleet]
|
|
summary: List stacks on a fleet node
|
|
description: Returns stack names from a specific fleet node.
|
|
parameters:
|
|
- name: nodeId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"200":
|
|
description: Array of stack names.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
type: string
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
"502":
|
|
description: Failed to reach remote node.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"503":
|
|
description: Remote node not configured.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/api/fleet/node/{nodeId}/stacks/{stackName}/containers:
|
|
get:
|
|
operationId: getFleetNodeStackContainers
|
|
tags: [Fleet]
|
|
summary: List containers in a fleet node stack
|
|
description: Returns containers for a specific stack on a specific fleet node.
|
|
parameters:
|
|
- name: nodeId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
- $ref: "#/components/parameters/stackName"
|
|
responses:
|
|
"200":
|
|
description: Array of container objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Container"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
"503":
|
|
description: Remote node not configured.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/api/fleet/update-status:
|
|
get:
|
|
operationId: getFleetUpdateStatus
|
|
tags: [Fleet]
|
|
summary: Fleet update status
|
|
description: Returns version comparison and active update status for all nodes. Compares each node's version against the gateway's version. Local rows include compose pin details; remote rows expose only the safe pin kind and blocked flag from each node's public meta.
|
|
responses:
|
|
"200":
|
|
description: Update status for all nodes.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
nodes:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
nodeId:
|
|
type: integer
|
|
name:
|
|
type: string
|
|
type:
|
|
type: string
|
|
enum: [local, remote]
|
|
version:
|
|
type: string
|
|
nullable: true
|
|
latestVersion:
|
|
type: string
|
|
updateAvailable:
|
|
type: boolean
|
|
updateStatus:
|
|
type: string
|
|
nullable: true
|
|
enum: [updating, completed, timeout, failed, null]
|
|
skipActive:
|
|
type: boolean
|
|
skippedVersion:
|
|
type: string
|
|
nullable: true
|
|
imagePinKind:
|
|
type: string
|
|
nullable: true
|
|
composeImageRef:
|
|
type: string
|
|
nullable: true
|
|
targetImageRef:
|
|
type: string
|
|
nullable: true
|
|
updateBlocked:
|
|
type: boolean
|
|
updateBlockedReason:
|
|
type: string
|
|
nullable: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/fleet/nodes/{nodeId}/skip-version:
|
|
post:
|
|
operationId: skipNodeVersion
|
|
tags: [Fleet]
|
|
summary: Skip a node version
|
|
description: Marks a version as skipped for a node, hiding the update prompt and excluding it from update-all. Admin only.
|
|
parameters:
|
|
- name: nodeId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [version]
|
|
properties:
|
|
version:
|
|
type: string
|
|
description: Semver version string to skip.
|
|
responses:
|
|
"204":
|
|
description: Skip recorded.
|
|
"400":
|
|
description: Invalid parameters.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: Node not found.
|
|
delete:
|
|
operationId: unskipNodeVersion
|
|
tags: [Fleet]
|
|
summary: Clear a node version skip
|
|
description: Removes the skipped-version record for a node. Admin only.
|
|
parameters:
|
|
- name: nodeId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"204":
|
|
description: Skip cleared.
|
|
"400":
|
|
description: Invalid parameters.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: Node not found.
|
|
|
|
/api/fleet/update-status/release-notes:
|
|
get:
|
|
operationId: getFleetReleaseNotes
|
|
tags: [Fleet]
|
|
summary: Release notes for the latest Sencho version
|
|
description: Returns the GitHub release body and URL for the latest Sencho release, used by the Changelog tab.
|
|
responses:
|
|
"200":
|
|
description: Release notes.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
releaseNotes:
|
|
type: string
|
|
nullable: true
|
|
htmlUrl:
|
|
type: string
|
|
nullable: true
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
|
|
/api/fleet/nodes/{nodeId}/update:
|
|
post:
|
|
operationId: triggerNodeUpdate
|
|
tags: [Fleet]
|
|
summary: Trigger node update
|
|
description: Initiates a self-update on the specified node. For remote nodes, sends the update command via the proxy. For local nodes, triggers the update directly.
|
|
parameters:
|
|
- name: nodeId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
"202":
|
|
description: Update initiated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
description: Node not found.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"409":
|
|
description: Update already in progress.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"503":
|
|
description: Self-update not available on the target node.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
|
|
/api/fleet/update-all:
|
|
post:
|
|
operationId: triggerFleetUpdateAll
|
|
tags: [Fleet]
|
|
summary: Update all outdated nodes
|
|
description: Triggers self-update on all remote nodes running an older version than the gateway. Forwards the hub compare target as `targetVersion` so semver-pinned remotes can repin before recreate. Local nodes are excluded from bulk updates.
|
|
responses:
|
|
"202":
|
|
description: Bulk update initiated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
updating:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Names of nodes where update was triggered.
|
|
skipped:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Names of nodes that were skipped.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
|
|
/api/fleet/snapshots:
|
|
post:
|
|
operationId: createFleetSnapshot
|
|
tags: [Fleet]
|
|
summary: Create fleet snapshot
|
|
description: Creates a point-in-time backup of all compose files across all nodes. Requires admin role.
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
description:
|
|
type: string
|
|
description: Optional description for the snapshot.
|
|
example: Pre-migration backup
|
|
responses:
|
|
"201":
|
|
description: Snapshot created.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/FleetSnapshot"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
get:
|
|
operationId: listFleetSnapshots
|
|
tags: [Fleet]
|
|
summary: List fleet snapshots
|
|
description: Returns paginated fleet snapshots.
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 50
|
|
maximum: 100
|
|
- name: offset
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 0
|
|
responses:
|
|
"200":
|
|
description: Paginated snapshot list.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [snapshots, total]
|
|
properties:
|
|
snapshots:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/FleetSnapshot"
|
|
total:
|
|
type: integer
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/fleet/snapshots/{id}:
|
|
get:
|
|
operationId: getFleetSnapshot
|
|
tags: [Fleet]
|
|
summary: Get snapshot details
|
|
description: Returns full snapshot details including all captured files grouped by node and stack.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Snapshot with file contents.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/FleetSnapshotDetail"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
operationId: deleteFleetSnapshot
|
|
tags: [Fleet]
|
|
summary: Delete snapshot
|
|
description: Permanently deletes a fleet snapshot. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Snapshot deleted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/fleet/snapshots/{id}/restore:
|
|
post:
|
|
operationId: restoreFleetSnapshot
|
|
tags: [Fleet]
|
|
summary: Restore from snapshot
|
|
description: Restores a specific stack on a specific node from the snapshot. Optionally redeploys after restore. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [nodeId, stackName]
|
|
properties:
|
|
nodeId:
|
|
type: integer
|
|
description: Target node ID to restore to.
|
|
stackName:
|
|
type: string
|
|
description: Stack to restore.
|
|
redeploy:
|
|
type: boolean
|
|
default: false
|
|
description: Whether to redeploy the stack after restoring files.
|
|
responses:
|
|
"200":
|
|
description: Stack restored.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessMessage"
|
|
example:
|
|
message: Stack restored successfully.
|
|
"400":
|
|
description: Missing required fields.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
description: One or more snapshot files for the stack could not be decrypted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [error, code]
|
|
properties:
|
|
error:
|
|
type: string
|
|
code:
|
|
type: string
|
|
enum: [SNAPSHOT_FILE_UNAVAILABLE]
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
/api/scheduled-tasks:
|
|
get:
|
|
operationId: listScheduledTasks
|
|
tags: [Scheduled Tasks]
|
|
summary: List scheduled tasks
|
|
description: Returns all scheduled tasks. Requires admin role.
|
|
responses:
|
|
"200":
|
|
description: Array of scheduled task objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ScheduledTask"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
operationId: createScheduledTask
|
|
tags: [Scheduled Tasks]
|
|
summary: Create scheduled task
|
|
description: |
|
|
Creates a new recurring task. Action-target rules:
|
|
- `restart` requires `target_type: stack` or `target_type: container` (with `target_id` and `node_id`)
|
|
- `auto_stop` and `auto_start` accept `target_type: stack` or `target_type: container`
|
|
- `snapshot` requires `target_type: fleet`
|
|
- `prune` requires `target_type: system`
|
|
|
|
Requires admin role.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name, target_type, action, cron_expression]
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: Nightly restart
|
|
target_type:
|
|
type: string
|
|
enum: [stack, fleet, system, container]
|
|
target_id:
|
|
type: string
|
|
description: Stack or container name (required when target_type is `stack` or `container`).
|
|
node_id:
|
|
type: integer
|
|
description: Target node ID (required when target_type is `stack` or `container`).
|
|
action:
|
|
type: string
|
|
enum: [restart, snapshot, prune]
|
|
cron_expression:
|
|
type: string
|
|
description: Standard 5-field cron expression.
|
|
example: "0 3 * * *"
|
|
enabled:
|
|
type: boolean
|
|
default: true
|
|
prune_targets:
|
|
type: array
|
|
items:
|
|
type: string
|
|
enum: [containers, images, networks, volumes]
|
|
description: Resources to prune (only for `prune` action).
|
|
target_services:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: Specific services to restart (only for `restart` action).
|
|
prune_label_filter:
|
|
type: string
|
|
description: Docker label filter for prune operations.
|
|
responses:
|
|
"201":
|
|
description: Task created.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ScheduledTask"
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/scheduled-tasks/{id}:
|
|
get:
|
|
operationId: getScheduledTask
|
|
tags: [Scheduled Tasks]
|
|
summary: Get scheduled task
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Scheduled task object.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ScheduledTask"
|
|
"400":
|
|
description: Invalid task ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
operationId: updateScheduledTask
|
|
tags: [Scheduled Tasks]
|
|
summary: Update scheduled task
|
|
description: Updates task configuration. Same validation rules as creation apply. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
target_type:
|
|
type: string
|
|
enum: [stack, fleet, system, container]
|
|
target_id:
|
|
type: string
|
|
description: Stack or container name when target_type is `stack` or `container`.
|
|
node_id:
|
|
type: integer
|
|
action:
|
|
type: string
|
|
enum: [restart, snapshot, prune]
|
|
cron_expression:
|
|
type: string
|
|
enabled:
|
|
type: boolean
|
|
prune_targets:
|
|
type: array
|
|
items:
|
|
type: string
|
|
target_services:
|
|
type: array
|
|
items:
|
|
type: string
|
|
prune_label_filter:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Task updated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ScheduledTask"
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
operationId: deleteScheduledTask
|
|
tags: [Scheduled Tasks]
|
|
summary: Delete scheduled task
|
|
description: Permanently deletes a scheduled task. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Task deleted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"400":
|
|
description: Invalid task ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/scheduled-tasks/{id}/toggle:
|
|
patch:
|
|
operationId: toggleScheduledTask
|
|
tags: [Scheduled Tasks]
|
|
summary: Toggle task enabled/disabled
|
|
description: Flips the enabled state of a scheduled task. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Task toggled. Returns updated task.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ScheduledTask"
|
|
"400":
|
|
description: Invalid task ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/scheduled-tasks/{id}/run:
|
|
post:
|
|
operationId: runScheduledTask
|
|
tags: [Scheduled Tasks]
|
|
summary: Run task immediately
|
|
description: Executes the scheduled task immediately, regardless of its cron schedule. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Task executed. Returns updated task with `last_run_at`.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ScheduledTask"
|
|
"400":
|
|
description: Invalid task ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/scheduled-tasks/{id}/runs:
|
|
get:
|
|
operationId: listScheduledTaskRuns
|
|
tags: [Scheduled Tasks]
|
|
summary: List task execution history
|
|
description: Returns paginated execution history for a scheduled task. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 50
|
|
- name: offset
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 0
|
|
responses:
|
|
"200":
|
|
description: Array of task run records.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ScheduledTaskRun"
|
|
"400":
|
|
description: Invalid task ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/scheduled-tasks/{id}/runs/export:
|
|
get:
|
|
operationId: exportScheduledTaskRuns
|
|
tags: [Scheduled Tasks]
|
|
summary: Export task history as CSV
|
|
description: Downloads the execution history for a scheduled task as a CSV file. Requires admin role.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: CSV file download.
|
|
content:
|
|
text/csv:
|
|
schema:
|
|
type: string
|
|
description: "CSV with columns: Timestamp, Source, Status, Duration (s), Details"
|
|
"400":
|
|
description: Invalid task ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ── Registries ──────────────────────────────────────────
|
|
/api/registries:
|
|
get:
|
|
operationId: listRegistries
|
|
tags: [Registries]
|
|
summary: List registries
|
|
description: |
|
|
Returns all configured private container registries. Secrets are never included in the response.
|
|
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
|
responses:
|
|
"200":
|
|
description: Array of registry objects.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Registry"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
operationId: createRegistry
|
|
tags: [Registries]
|
|
summary: Create registry
|
|
description: |
|
|
Adds a new private container registry. The secret is encrypted at rest using AES-256-GCM.
|
|
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RegistryCreate"
|
|
responses:
|
|
"201":
|
|
description: Registry created.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [id]
|
|
properties:
|
|
id:
|
|
type: integer
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/registries/{id}:
|
|
put:
|
|
operationId: updateRegistry
|
|
tags: [Registries]
|
|
summary: Update registry
|
|
description: |
|
|
Updates an existing registry. All fields are optional — only provided fields are changed.
|
|
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RegistryCreate"
|
|
responses:
|
|
"200":
|
|
description: Registry updated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"400":
|
|
description: Validation error.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
operationId: deleteRegistry
|
|
tags: [Registries]
|
|
summary: Delete registry
|
|
description: |
|
|
Removes a registry and its encrypted credentials.
|
|
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Registry deleted.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/SuccessBoolean"
|
|
"400":
|
|
description: Invalid registry ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/registries/{id}/test:
|
|
post:
|
|
operationId: testRegistry
|
|
tags: [Registries]
|
|
summary: Test registry connection
|
|
description: |
|
|
Tests connectivity and authentication against a configured registry.
|
|
Requires Admiral license and admin role. API tokens receive `SCOPE_DENIED`.
|
|
parameters:
|
|
- $ref: "#/components/parameters/idPath"
|
|
responses:
|
|
"200":
|
|
description: Test result.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
"400":
|
|
description: Invalid registry ID.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"403":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ── Image Updates ───────────────────────────────────────
|
|
/api/image-updates:
|
|
get:
|
|
operationId: getImageUpdates
|
|
tags: [Image Updates]
|
|
summary: Get image update status
|
|
description: Returns the update availability status for all stacks.
|
|
responses:
|
|
"200":
|
|
description: Array of update status objects per stack.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ImageUpdateStatus"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/image-updates/refresh:
|
|
post:
|
|
operationId: refreshImageUpdates
|
|
tags: [Image Updates]
|
|
summary: Trigger image update check
|
|
description: |
|
|
Triggers a background check for newer image versions across all stacks.
|
|
Rate limited to one refresh per 10 minutes. Requires admin role.
|
|
responses:
|
|
"200":
|
|
description: Refresh started.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
example: Image update check started in background.
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"429":
|
|
description: Rate limited — wait at least 10 minutes between refreshes.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Error"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/image-updates/status:
|
|
get:
|
|
operationId: getImageUpdateCheckStatus
|
|
tags: [Image Updates]
|
|
summary: Get scanner cadence status
|
|
description: Returns the scanner's current status including scheduling mode and next check time.
|
|
responses:
|
|
"200":
|
|
description: Scanner status.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ImageUpdateCheckStatus"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
|
|
/api/image-updates/interval:
|
|
put:
|
|
operationId: setImageUpdateCheckSchedule
|
|
tags: [Image Updates]
|
|
summary: Set the detection schedule
|
|
description: Configure the image-update check cadence. In interval mode only `minutes` is required. In cron mode also send `mode` and `cron`.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [minutes]
|
|
properties:
|
|
minutes:
|
|
type: integer
|
|
minimum: 15
|
|
maximum: 1440
|
|
description: Fallback interval in minutes (used in interval mode, or as fallback when cron is unset).
|
|
mode:
|
|
type: string
|
|
enum: [interval, cron]
|
|
description: Scheduling mode. Omit to leave unchanged.
|
|
cron:
|
|
type: string
|
|
description: 5-field cron expression (required when mode is 'cron'). Nicknames like @daily are accepted.
|
|
responses:
|
|
"200":
|
|
description: Schedule updated.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ImageUpdateCheckStatus"
|
|
"400":
|
|
$ref: "#/components/responses/Forbidden"
|
|
"401":
|
|
$ref: "#/components/responses/Unauthorized"
|
|
"403":
|
|
description: Non-admin users cannot change the schedule.
|
|
|
|
/api/registries/{id}/tags:
|
|
get:
|
|
tags: [Registries]
|
|
summary: List tags for a repository on a configured registry
|
|
description: |
|
|
Hub-only. Admin only. Credentials are resolved by exact registry ID.
|
|
Upstream registry auth failures return 424 with a REGISTRY_* code, never HTTP 401.
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: integer }
|
|
- name: repository
|
|
in: query
|
|
required: true
|
|
schema: { type: string }
|
|
description: Path-safe repository name (no host or scheme)
|
|
- name: cursor
|
|
in: query
|
|
required: false
|
|
schema: { type: string }
|
|
- name: limit
|
|
in: query
|
|
required: false
|
|
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
|
|
responses:
|
|
'200':
|
|
description: Tag page
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RegistryTagList"
|
|
'400':
|
|
description: Invalid repository or response
|
|
'404':
|
|
description: Registry or repository not found
|
|
'424':
|
|
description: Upstream registry auth or policy failure (not a Sencho session 401)
|
|
'502':
|
|
description: Upstream registry transport failure
|