Skip to main content

External API (Beta)

Embedded Cluster exposes a small, stable HTTP API on every controller node for cross-node automation. This API is intended for headless installs, programmatic node joins, and external orchestration.

Overview

  • The API listens on the controller's internal IP on TCP port 30081 by default. Override at install with --api-port.
  • All traffic is TLS-encrypted using the certificates configured at install time and is served by the daemon.
  • All non-health endpoints require a JWT bearer token obtained from POST /v1/auth/token.
  • The API is reachable from any host in the cluster network — not from outside the cluster unless you explicitly expose it.

The full Embedded Cluster API (used by the local web UI) is served on a Unix socket and is not exposed on the network. Only the endpoints documented below are reachable on port 30081.

Authentication

Obtain a token using the installer password:

curl -X POST https://<controller-ip>:30081/v1/auth/token \
-H 'Content-Type: application/json' \
-d '{"password":"<installer-password>"}'

Response:

{ "token": "eyJhbGciOi..." }

The token is a JWT valid for 24 hours. Pass it as Authorization: Bearer <token> on subsequent requests.

Endpoints

GET /v1/health

Liveness probe. No authentication required.

curl https://<controller-ip>:30081/v1/health

Response:

{ "status": "ok" }

GET /v1/version

Returns the running daemon version. No authentication required.

curl https://<controller-ip>:30081/v1/version

Response:

{
"version": "3.0.0",
"commit": "abcdef0",
"buildDate": "2026-01-15_12:00:00"
}

POST /v1/auth/token

See Authentication.

GET /v1/nodes

Returns the list of nodes in the cluster.

Request:

curl https://<controller-ip>:30081/v1/nodes \
-H "Authorization: Bearer $TOKEN"

Response:

{
"nodes": [
{
"name": "controller-0",
"labels": {
"node-role.kubernetes.io/control-plane": "",
"kots.io/embedded-cluster-role-0": "controller"
},
"annotations": { ... },
"roles": ["controller"],
"internalIP": "10.0.0.11",
"ready": true
}
]
}

Field reference:

FieldDescription
nameKubernetes node name.
labelsAll labels on the node.
annotationsAll annotations on the node.
rolesThe vendor-defined role names assigned at join time (from the EC config spec.roles).
internalIPThe node's internal IP address.
readytrue when the node's Ready condition is True.

Automation should poll this endpoint to verify the cluster is ready to issue join commands. After the k0s install completes, the controller takes a short time to report Ready in the cluster. Calls to POST /v1/create-join-command fail until at least one controller has ready: true, so automation should poll GET /v1/nodes first and only request join commands once a Ready controller is present.

POST /v1/create-join-command

Returns the shell commands a remote host must run to join the cluster: download a join bundle, extract it, and run node join. The download command embeds a short-lived bearer token (1 hour TTL).

Request:

curl -X POST https://<controller-ip>:30081/v1/create-join-command \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"roles":["controller"]}'

The roles array must contain one or more role names defined in the application's EC config (spec.roles.controller.name and spec.roles.custom[].name). Multiple roles can be assigned to the same node; the joined node will carry the union of each role's labels.

Response:

{
"joinSteps": [
{
"description": "Download the join bundle",
"command": "curl -H \"Authorization: Bearer ...\" https://10.0.0.11:30081/v1/join-bundle?roles=controller -o APP_SLUG-join-controller-20260115-120000.tar.gz"
},
{
"description": "Extract the bundle",
"command": "tar xzf APP_SLUG-join-controller-20260115-120000.tar.gz"
},
{
"description": "Run the join command",
"command": "sudo ./APP_SLUG node join"
}
]
}

GET /v1/join-bundle?roles=<role1,role2,...>

Streams a gzip tarball containing everything a new node needs to join the cluster: binaries, EC bundle, license, signed cluster seed, and a k0s join token. The roles query parameter accepts a comma-separated list of role names.

curl https://<controller-ip>:30081/v1/join-bundle?roles=controller \
-H "Authorization: Bearer $TOKEN" \
-o APP_SLUG-join-controller.tar.gz

Typically you do not call this endpoint directly — the curl command returned by POST /v1/create-join-command calls it for you with a short-lived, audience-scoped token.

The request requires a multi-node-enabled license.

See also

  • Headless joins in Access and manage embedded clusters — end-to-end walkthrough that composes these endpoints to join a node without the web UI.
  • roles in Embedded Cluster Config — declare the role names used in POST /v1/create-join-command and GET /v1/join-bundle.