Skip to main content

API Endpoints

The API server listens on :8443 with TLS. Authenticated endpoints require either HMAC-signed requests or a JWT bearer token.

Unauthenticated​

MethodPathDescription
GET/healthzHealth check
GET/v1/versionAPI version
POST/v1/bootstrapCreate initial admin user
POST/v1/iam/loginLogin with username/password (returns JWT)
GET/v1/caDownload CA certificate (auto-generated certs only)
GET/v1/agent/binaryDownload agent binary
POST/v1/nodes/joinNode self-registration with a join token

Internal (mTLS only)​

Used by agents. Requires mutual TLS with a valid client certificate.

MethodPathDescription
POST/v1/internal/heartbeatAgent heartbeat
GET/v1/internal/tasks/{nodeId}Poll pending tasks
POST/v1/internal/tasks/{taskId}/resultReport task result

Nodes​

MethodPathDescription
POST/v1/nodes/deployDeploy node server-side (via SSH profile or inline credentials)
POST/v1/nodes/tokenGenerate a join token for a node to self-register with (returns id, token, expires_at)
DELETE/v1/nodes/token/{id}Revoke an unused join token and drop the WireGuard peer it reserved; 404 if already used
POST/v1/nodes/eventsRecord a node event
GET/v1/nodes/eventsList node events (deploy steps, join failures, SSH updates). Optional ?node_id= or ?identifier= (IP or hostname); without either, every node's events — see note below
POST/v1/nodesCreate node
GET/v1/nodesList nodes
GET/v1/nodes/{id}Get node
PATCH/v1/nodes/{id}Update the node's display name ({"name": "..."}) — see note below
POST/v1/nodes/{id}/ipChange IP and/or pinned interface ({"ip_address", "interface", "regenerate_cert"})
POST/v1/nodes/{id}/drainCordon a node and have its orchestrators move the workloads off
POST/v1/nodes/{id}/cordonMark a node unschedulable, leaving its workloads alone
POST/v1/nodes/{id}/uncordonPut a cordoned or drained node back into service
POST/v1/nodes/{id}/gpu-overcommitSet GPU overcommit factor ({"gpu_overcommit_factor": N}, N ∈ 8)
POST/v1/nodes/{id}/update-agentQueue an agent binary update on the node (no SSH; node must be ready)
POST/v1/nodes/{id}/update-sshUpdate the agent binary and client certificate over SSH from the control plane, in the background (202); for an agent that is down. Takes the SSH credential fields below
POST/v1/nodes/{id}/os-updateQueue a full OS package upgrade; node enters needs_reboot if a reboot is required
POST/v1/nodes/{id}/check-updatesRe-check for OS package updates now, instead of waiting for the daily check
POST/v1/nodes/{id}/rebootQueue a reboot
POST/v1/nodes/{id}/stopStop the agent on a node; it reports offline and stops. The machine and its workloads keep running
POST/v1/nodes/{id}/startStart the agent on a node over SSH from the control plane, in the background (202); a stopped node has no agent to take a task. Takes the SSH credential fields below
POST/v1/nodes/{id}/logsQueue an agent log fetch (?since=TIMESTAMP, else ?lines=N — default 100, max 10000); returns a task ID
GET/v1/nodes/{id}/logs/{taskId}Poll the log fetch result
DELETE/v1/nodes/{id}Delete node
POST/v1/nodes/{id}/certIssue client certificate
POST/v1/nodes/{id}/cert/revokeRevoke certificates
POST/v1/nodes/{id}/unmanagedScan for workloads DockNimbus did not create (?backend=swarm|k8s); returns a task_id
GET/v1/nodes/{id}/unmanaged/{taskId}Result of that scan — see note below

Listing node events​

GET /v1/nodes/events takes optional filters, so the UI's activity dock can read every node at once and then poll for what is new:

ParameterMeaning
node_idOne node. Its events recorded against its address before it had an ID are included
identifierOne address or hostname, for events from before a node existed
sinceRFC3339 timestamp; only events after it
limitKeep the newest N (max 1000). Applied by default (200) only when no node is named

Events come back oldest first. A request naming no node and no identifier reads across every node, which is why it is capped.

SSH credentials​

POST /v1/nodes/deploy and POST /v1/nodes/{id}/update-ssh both have the control plane open the SSH connection to the node, and take the same credential fields:

FieldMeaning
ssh_profileName of a stored SSH profile
ssh_userSSH user (default root); a non-root user needs passwordless sudo
ssh_portSSH port (default 22)
ssh_keyPrivate key content, not a path
ssh_passwordSSH password

Values given inline override the profile's. With none, the control plane's own ssh-agent and ~/.ssh/id_* keys are tried.

update-ssh answers 202 and runs in the background. It records ssh_update_started, then ssh_update_completed or ssh_update_failed (with the reason) as node events. It refuses up front with 409 a node that is being removed or has not reported its architecture yet, and with 400 an unknown profile.

Unmanaged resource scans​

POST /v1/nodes/{id}/unmanaged queues a scan on the node and returns a task ID. Poll GET /v1/nodes/{id}/unmanaged/{taskId} until status is completed, then read resources; a failed scan returns error instead.

The result is never stored. The task row is only the transport, and the API deletes it as soon as it hands the result over, so a scan is a live look at the runtime rather than an inventory that can go stale. Poll again for a fresh answer — the same task ID cannot be read twice.

The scan reports only what DockNimbus did not create. Its own workloads are excluded, which is not simply a matter of one label: instances, the load balancer and DNS carry different nimbus label values, services belonging to a DockNimbus compose stack carry no nimbus label at all, and on Kubernetes the cluster's system namespaces and DockNimbus's own ingress namespace are excluded too.

Node name vs. hostname​

A node carries two separate labels, and only one of them is yours to set:

FieldSet byMeaning
nameYou, via PATCH /v1/nodes/{id}A display label used to identify the node in the CLI and dashboard.
hostnameDiscoveredThe machine's own OS hostname. Docker Swarm and Kubernetes address nodes by this, so nimbus keeps it in step with what the node actually reports.

PATCH /v1/nodes/{id} changes only name. It does not rename the machine, and it does not touch Docker Swarm or Kubernetes — a node renamed this way keeps the same hostname everywhere else.

To rename the machine itself, change its OS hostname on the node (hostnamectl set-hostname ...) and restart Docker so the swarm picks it up. Nimbus notices on the next heartbeat from the swarm manager and updates its own record; swarm membership is tracked by the Docker node ID, which survives the rename.

Changing a node's IP​

POST /v1/nodes/{id}/ip takes at least one of ip_address or interface. regenerate_cert is accepted for the control-plane node only (400 otherwise) and reissues the API server certificate for the new address, hot-swapping it without a restart.

The response is the updated node, plus a certificate object when a reissue was requested:

{
"data": {
"id": "node-abc123",
"ip_address": "192.168.1.50",
"certificate": { "reissued": true, "added_sans": ["192.168.1.50"] }
}
}

The IP change and the reissue are reported independently: if the certificate fails, the response is still 200 with certificate.error set, because the address change has already been committed. See Handling Dynamic Node IPs.

Swarm Groups​

MethodPathDescription
POST/v1/swarmsCreate swarm group
GET/v1/swarmsList swarms
GET/v1/swarms/{id}Get swarm
POST/v1/swarms/{id}/joinAdd node to swarm
POST/v1/swarms/{id}/leaveRemove node from swarm
DELETE/v1/swarms/{id}Delete swarm (202 while its nodes leave, 200 when already gone)
GET/v1/swarms/{id}/membersList swarm members
POST/v1/swarms/{id}/lbDeploy load balancer
DELETE/v1/swarms/{id}/lbRemove load balancer

Compute​

MethodPathDescription
POST/v1/compute/instancesCreate instance
GET/v1/compute/instancesList instances
GET/v1/compute/instances/{id}Get instance
DELETE/v1/compute/instances/{id}Terminate instance
POST/v1/compute/instances/{id}/stopStop instance
POST/v1/compute/instances/{id}/startStart instance
POST/v1/compute/instances/{id}/scaleScale replicas
POST/v1/compute/instances/{id}/logsQueue log fetch (?lines=N or ?since=TIMESTAMP)
GET/v1/compute/instances/{id}/logs/{taskId}Poll log fetch result
POST/v1/compute/instances/{id}/volumesAttach volume
DELETE/v1/compute/instances/{id}/volumes/{volumeId}Detach volume
GET/v1/compute/instance-typesList instance types

Kubernetes (K3s)​

MethodPathDescription
POST/v1/kubernetes/clustersCreate cluster
GET/v1/kubernetes/clustersList clusters
GET/v1/kubernetes/clusters/{id}Get cluster
DELETE/v1/kubernetes/clusters/{id}Delete cluster
GET/v1/kubernetes/clusters/{id}/kubeconfigGet kubeconfig
POST/v1/kubernetes/clusters/{id}/nodesAdd worker node
GET/v1/kubernetes/clusters/{id}/nodesList cluster nodes
DELETE/v1/kubernetes/clusters/{id}/nodes/{nodeId}Remove worker node
POST/v1/kubernetes/clusters/{id}/lbDeploy EasyHAProxy on the cluster
DELETE/v1/kubernetes/clusters/{id}/lbRemove it
POST/v1/kubernetes/clusters/{id}/volumesAttach NFS volume (PV+PVC)
DELETE/v1/kubernetes/clusters/{id}/volumes/{volumeId}Detach volume

Volumes​

MethodPathDescription
POST/v1/volumesCreate volume
GET/v1/volumesList volumes
GET/v1/volumes/{id}Get volume
DELETE/v1/volumes/{id}Delete volume

Secrets and configs​

The same endpoints under /v1/secrets and /v1/configs. Values are base64 in data. A secret's is never returned, a config's is returned by GET /v1/configs/{id}. {id} may also be a name, as long as only one has it.

MethodPathDescription
POST/v1/secretsCreate: name, swarm_id or cluster_id (+ namespace), data
GET/v1/secretsList, each with services (the services using it)
GET/v1/secrets/{id}Get one
PUT/v1/secrets/{id}Replace the value (data); the services using it are redeployed
DELETE/v1/secrets/{id}Delete; 409 naming the services while any uses it

Create body: name, node_id, folder, and type: "nfs" (default) or "local". The type is fixed once created; volumes report it as type. A local volume has no server_ip.

S3 (RustFS)​

MethodPathDescription
POST/v1/s3/instancesDeploy RustFS instance
GET/v1/s3/instancesList instances
DELETE/v1/s3/instances/{id}Delete instance

Deploy body: name, swarm_id and volume_id are required; password (at least 8 characters, generated when omitted) and cert_id (enables console SSO) are optional. Instances report domain (the S3 API) and console_domain (the web console).

Services​

A service is a deployed workload bundle: a Compose stack on a swarm, or a Kubernetes manifest on a cluster.

MethodPathDescription
POST/v1/servicesDeploy a compose stack, or apply a k8s manifest
GET/v1/servicesList services
GET/v1/services/{id}Get service, with its applied objects
PUT/v1/services/{id}Replace the spec and redeploy (a manifest re-applies and prunes)
POST/v1/services/{id}/stopStop service (a manifest's objects are deleted)
POST/v1/services/{id}/startStart service (a manifest is re-applied)
DELETE/v1/services/{id}Remove service
POST/v1/services/{id}/logsQueue a log read; answers with a task_id
GET/v1/services/{id}/logs/{taskId}Result of a queued log read

Specs are base64-encoded on the way in, on POST and PUT alike, and returned as readable YAML on the way out. A deploy body carries exactly one backend. Compose:

{ "name": "web", "swarm_id": "swarm-3f1d5a37", "compose_file": "<base64 YAML>" }

Kubernetes:

{ "name": "api", "cluster_id": "cl-7f2b18d0", "manifest": "<base64 YAML>" }

A service stores one spec; the target says how to read it. On the wire it is named for what it is, so a service carries swarm_id with compose_file, or cluster_id with manifest, and — for a manifest — applied_objects: the objects kubectl reported applying (kind, name, namespace, api_version). That list is what Nimbus monitors, prunes against on a re-apply, and deletes on removal.

Gateway / Load Balancer​

MethodPathDescription
GET/v1/gatewayRouting table + every load balancer
GET/v1/loadbalancersList load balancers
GET/v1/loadbalancers/{id}Get load balancer

GET /v1/gateway answers with {"routes": [...], "load_balancers": [...]}. Both lists are always present, empty when there is nothing to report. A load balancer names the swarm or the cluster it serves in swarm_id or cluster_id — exactly one of the two is set.

SSH Profiles​

Manage encrypted SSH credential profiles for manifest-based node provisioning.

MethodPathDescription
POST/v1/ssh-profilesCreate SSH profile
GET/v1/ssh-profilesList profiles
GET/v1/ssh-profiles/{id}Get profile
DELETE/v1/ssh-profiles/{id}Delete profile

Sensitive fields (private key, password) are encrypted with the CA key and never returned in API responses. List and get responses include has_key and has_password booleans.

Tags​

MethodPathDescription
PUT/v1/tagsSet a tag on a resource
GET/v1/tagsList resource IDs by tag
DELETE/v1/tagsDelete a tag

IAM​

MethodPathDescription
POST/v1/iam/usersCreate user
POST/v1/iam/users/{id}/keysGenerate API key pair
DELETE/v1/iam/users/{id}/keys/{keyId}Delete API key
POST/v1/iam/tokenGet token (HMAC-authenticated)
POST/v1/iam/users/{id}/passwordSet password

Cleanup​

MethodPathDescription
POST/v1/cleanup/{resourceType}/{resourceId}Force-clean stuck resources