cardinal-wings is a REST API daemon that lets panels and frontends manage cardinal containers, images, blueprints, services and a multi-node cluster over HTTP — no SSH, no CLI.
wings sits on top of cardinal's Docker-compatible HTTP API and gives it a panel-oriented REST surface plus a cluster view. It reuses what cardinal already does — it does not reinvent the container runtime or the orchestrator.
Full container lifecycle: list with filters and pagination, create, inspect, start/stop/restart/kill, remove, stats, and logs with SSE streaming.
Image list/pull/tag/push and search, the blueprint catalog with async install, plus service and function management through cardinal's orchestrator.
`?node=
Long operations — blueprint install, image pull — return a task id and run in the background, pollable at `/v1/tasks`.
Multiple keys, each with a role (read-only or admin), rate limiting, TLS support and loopback-by-default — plus `/v1/self` so the UI knows what it can do.
Multi-node Prometheus metrics (admin-only) and a stable error envelope — `{"error":{"code":"…","message":"…"}}` — across every endpoint.
Open a shell in any container via websocket (`/terminal/ws`) or SSE — a real terminal for the panel, no extra dependencies.
CPU/memory snapshots stream as SSE (`/stats?stream=1`), and container events push to the panel over `/v1/events`.
Task completion and container events POST to panel URLs with a shared secret — notifications without polling.
Base URL http://127.0.0.1:8080 (default). Every request
except /v1/ping and /healthz requires
Authorization: Bearer <key>. Every resource endpoint
accepts ?node=<name> to target a specific cardinal
node — see the cluster guide below for wiring a hub over many nodes.
{"error":{"code":"…","message":"…"}}
with one of bad_request, unauthorized,
forbidden, not_found, conflict,
method_not_allowed, not_implemented,
upstream_error, internal.
| Method | Path | Description |
|---|---|---|
GET | /v1/ping | Liveness — returns pong, no auth |
GET | /healthz | Health — 200 ok or 503 degraded when local cardinal is down, no auth |
GET | /v1/version | Wings version |
GET | /v1/self | Current key role (admin / readonly) for UI gating |
GET | /v1/nodes | Cluster nodes with live health from the background checker |
GET | /v1/system/info | Dashboard aggregate — per-node containers/images/cpu/mem + totals |
GET | /v1/events | Container events streamed as SSE for live UI updates |
GET | /v1/metrics | Multi-node Prometheus metrics (admin only) |
| Method | Path | Description |
|---|---|---|
GET | /v1/containers | List — filters ?all=1, ?state=, ?image=, ?search=, ?sort=, ?limit=, ?offset= |
POST | /v1/containers | Create (admin) — {"image","name","ports","restart",…} |
GET | /v1/containers/{id} | Inspect — full container state |
POST | /v1/containers/{id}/start | Start (admin) |
POST | /v1/containers/{id}/stop | Stop (admin) |
POST | /v1/containers/{id}/restart | Restart (admin) |
POST | /v1/containers/{id}/kill | Force-kill (admin) |
DELETE | /v1/containers/{id} | Remove — ?force=1 (admin) |
GET | /v1/containers/{id}/stats | CPU/mem snapshot — ?stream=1 streams as SSE |
GET | /v1/containers/{id}/logs | Logs — ?tail=N, ?follow=1 streams as SSE |
POST | /v1/containers/{id}/exec | Run a command, returns an exec id (admin) |
POST | /v1/containers/{id}/exec/stream | Run a command and stream output as SSE (admin) |
POST | /v1/containers/{id}/terminal | Open an interactive shell session (admin) |
POST | /v1/containers/{id}/terminal/input | Write to the session's stdin (admin) |
GET | /v1/containers/{id}/terminal/stream | Session output as SSE (ring buffer, last 200 lines) |
GET | /v1/containers/{id}/terminal/ws | Full terminal over websocket — text frames in, output back (admin) |
GET | /v1/containers/{id}/fs/ls | File browser — list ?path= |
GET | /v1/containers/{id}/fs/cat | File browser — read a file ?path= |
GET | /v1/containers/{id}/fs/tree | File browser — directory tree ?path= |
POST | /v1/containers/{id}/cp | Copy files in/out — body {"src","dst"} (admin) |
Images
Method Path Description GET/v1/imagesList local images GET/v1/images/{ref}Inspect an image GET/v1/images/searchDocker Hub search — ?q=postgres POST/v1/images/{ref}/pullPull — async, returns a task id (admin) POST/v1/images/{ref}/tagTag — ?repo=…&tag=… (admin) POST/v1/images/{ref}/pushPush to a registry (admin) DELETE/v1/images/{ref}Remove an image (admin)
Blueprints
Method Path Description GET/v1/blueprintsBlueprint catalog GET/v1/blueprints/{name}Blueprint detail POST/v1/blueprints/{name}/installInstall — async, poll the returned task id (admin) POST/v1/blueprints/{name}/uninstallUninstall — async (admin)
Tasks (async jobs)
Method Path Description GET/v1/tasksList tasks — including finished ones persisted across restarts GET/v1/tasks/{id}Task status, output and live pull/install progress
Services & functions
Method Path Description GET/v1/servicesList services POST/v1/servicesCreate — {"name","image","replicas","ports"} (admin) POST/v1/services/{name}/scaleScale — {"replicas":3} (admin) DELETE/v1/services/{name}Remove a service (admin) GET/v1/functionsList functions POST/v1/functionsCreate — {"name","image"} (admin) POST/v1/functions/{name}/invokeInvoke — {"data"} payload DELETE/v1/functions/{name}Remove a function (admin)
Cluster
Method Path Description GET/v1/cluster/healthFleet health across all configured nodes GET/v1/cluster/replicasService replicas across the cluster GET/v1/cluster/containersContainers across all nodes
Examples Every call, with curl.
# liveness — no token needed
curl localhost:8080/v1/ping # pong
curl localhost:8080/healthz # 200 ok / 503 degraded
# everything else requires a Bearer key
curl -H "Authorization: Bearer KEY" localhost:8080/v1/version
curl -H "Authorization: Bearer KEY" localhost:8080/v1/self
curl -H "Authorization: Bearer KEY" localhost:8080/v1/system/info
# list running containers (filters: ?all=1, ?state=, ?image=, ?search=, ?sort=, ?limit=)
curl -H "Authorization: Bearer KEY" "localhost:8080/v1/containers?state=running&limit=50"
# create (admin)
curl -X POST -H "Authorization: Bearer KEY" -H "Content-Type: application/json" \
-d '{"image":"nginx:latest","name":"web","ports":["8080:80"],"restart":"always"}' \
localhost:8080/v1/containers
# lifecycle, live logs, live stats
curl -X POST -H "Authorization: Bearer KEY" localhost:8080/v1/containers/web/start
curl -N -H "Authorization: Bearer KEY" "localhost:8080/v1/containers/web/logs?follow=1"
curl -H "Authorization: Bearer KEY" "localhost:8080/v1/containers/web/stats?stream=1"
# exec: run a command and stream output as SSE (admin)
curl -N -X POST -H "Authorization: Bearer KEY" -H "Content-Type: application/json" \
-d '{"Cmd":["tail","-f","/var/log/app.log"]}' \
localhost:8080/v1/containers/web/exec/stream
# interactive terminal: open a session, then bridge a websocket to it
curl -X POST -H "Authorization: Bearer KEY" localhost:8080/v1/containers/web/terminal
# ws://localhost:8080/v1/containers/web/terminal/ws (text frames → stdin, output ← frames)
# pull an image — async, returns a task id to poll
curl -X POST -H "Authorization: Bearer KEY" -H "Content-Type: application/json" \
-d '{"image":"postgres:16"}' \
localhost:8080/v1/images/postgres:16/pull
# → {"task_id":"task-3","action":"pull","image":"postgres:16"}
# poll the task — status + live pull progress
curl -H "Authorization: Bearer KEY" localhost:8080/v1/tasks/task-3
# install a blueprint — also async
curl -X POST -H "Authorization: Bearer KEY" -H "Content-Type: application/json" \
-d '{"memory":"2g","cpus":"2","env":["EULA=TRUE"]}' \
localhost:8080/v1/blueprints/minecraft-server/install
# target any node with ?node= — or aggregate the whole fleet
curl -H "Authorization: Bearer KEY" "localhost:8080/v1/containers?node=node-2"
curl -H "Authorization: Bearer KEY" localhost:8080/v1/nodes
curl -H "Authorization: Bearer KEY" localhost:8080/v1/cluster/health
curl -H "Authorization: Bearer KEY" localhost:8080/v1/system/info
curl -H "Authorization: Bearer KEY" localhost:8080/v1/metrics # Prometheus (admin)
Releases Built and published automatically.
Pushing a v* tag runs the build pipeline — lint, race-tested
tests, linux/amd64 + arm64 binaries with the version embedded — and
publishes a GitHub Release.
Install the latest release.
One command installs the daemon and its systemd unit. Point a panel
at it with a Bearer token from the config.
curl -fsSL https://github.com/animesao/cardinal-wings/releases/latest/download/install.sh | bash
cardinal-wings on GitHub
API reference
Docs Built for panel developers.
The full reference above with curl examples, an OpenAPI schema for
generating a client, and a step-by-step guide to wiring wings as a
hub over many cardinal nodes. Wings is MIT licensed — the
contributing guide and security policy are right next to the docs.
Roadmap What is next.
The wings backend has everything a panel needs; the next step is the
panel itself. One remaining upstream item: true PTY allocation in
cardinal for full terminal emulation (`vim`, `htop`). The API is
still experimental and details can change between versions.