Clusters
Register and manage target Kubernetes clusters
Clusters API
Register remote Kubernetes clusters as deployment targets for multi-cluster GitOps. All endpoints require a valid session and the noted permission.
Endpoints
List Clusters
GET /api/v1/clustersRequires cluster:read. Each cluster is enriched with a live probe of its API
server (version, provider, node count, resource usage, deployment health), and
the probe result refreshes the stored status and last_seen.
| Query param | Description |
|---|---|
shallow | 1 skips live probing and returns stored data only — cheap existence checks |
Response — 200 OK
{
"clusters": [
{
"id": "9f1c2a4e-...",
"name": "staging",
"server_url": "https://staging.example.com:6443",
"namespace": "default,team-a",
"status": "connected",
"version": "v1.29.1",
"provider": "eks",
"region": "us-east-1",
"node_count": 6,
"cpu_usage": 34,
"memory_usage": 61,
"deployment_count": 12,
"healthy_deployments": 11,
"last_seen": "2026-09-20T14:00:00Z",
"created_at": "2026-01-12T11:00:00Z"
}
]
}status is connected, disconnected, or unknown (never probed).
cpu_usage/memory_usage are whole-percent utilisation across nodes.
Register Cluster
POST /api/v1/clustersRequires cluster:register. Request bodies are capped at 1 MiB.
Request Body
{
"name": "staging",
"server_url": "https://staging.example.com:6443",
"kubeconfig": "apiVersion: v1\nkind: Config\n...",
"namespaces": ["default", "team-a"]
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name — DNS-1123 label (lowercase, digits, dashes; max 63 chars) |
server_url | string | Yes | API server URL — must be https:// |
kubeconfig | string | No | Raw kubeconfig YAML. Omit for in-cluster (the server's own service account) |
namespaces | string[] | No | Scope the cluster to these namespaces; empty means cluster-wide |
The kubeconfig is validated at registration: unparseable configs and configs
using exec or auth-provider credential plugins are rejected with 400.
Accepted kubeconfigs are sealed with AES-256-GCM before storage.
Response — 201 Created
{
"cluster": {
"id": "9f1c2a4e-...",
"name": "staging",
"server_url": "https://staging.example.com:6443",
"namespace": "default,team-a",
"status": "unknown",
"created_at": "2026-09-20T14:00:00Z"
}
}Registration does not probe the cluster — the first GET /clusters does.
Use POST /clusters/test to verify connectivity before registering.
Errors
| Status | Description |
|---|---|
400 | Missing/invalid name, non-HTTPS server_url, invalid namespace, or rejected kubeconfig |
401 | No valid session |
403 | Missing cluster:register permission |
409 | Cluster name already registered |
413 | Request body over 1 MiB |
Test Cluster Connection
POST /api/v1/clusters/testRequires cluster:register. Dry-run connectivity check against supplied
credentials — nothing is stored. Always returns 200; the ok field carries
the result so clients can render it inline.
Request Body
{
"server_url": "https://staging.example.com:6443",
"kubeconfig": "apiVersion: v1\nkind: Config\n..."
}| Field | Type | Required | Description |
|---|---|---|---|
server_url | string | No | Overrides the kubeconfig's cluster endpoint; must be https:// when set |
kubeconfig | string | No | Raw kubeconfig YAML. Omit to test the server's own in-cluster credentials |
Response — 200 OK
{
"ok": true,
"version": "v1.29.1",
"namespaces": ["default", "kube-system", "team-a"]
}{
"ok": false,
"error": "cannot reach API server: context deadline exceeded"
}namespaces lists what the credentials can see — use it to populate a
namespace picker. If the credentials can't list namespaces the test still
succeeds but namespaces is omitted.
Security
- Credentials sealed at rest — kubeconfigs are encrypted with AES-256-GCM
(
enc:v1:format) keyed byWARDN_CREDENTIALS_KEYbefore hitting Postgres - No executable credentials — kubeconfigs using
execorauth-providerplugins are rejected; the server never runs commands from user-supplied config - HTTPS only —
server_urlmust use TLS; plaintext endpoints are refused - Least privilege — use service accounts with only the permissions Wardn needs
- Rotation — rotate
WARDN_CREDENTIALS_KEYby re-sealing stored kubeconfigs; rotate cluster credentials via re-registration
Related endpoints
GET /api/v1/clusters/{name}/namespaces # live namespace list (cluster:read)
POST /api/v1/clusters/{name}/shell # spawn a shell pod (cluster:shell)The shell pod's ServiceAccount (view vs admin) is picked from the caller's effective permissions — a read-only user gets a shell that physically cannot write.
Related
- Multi-Cluster Guide — step-by-step tutorial
- Configuration —
destination.clusterfield reference - Server Overview — database schema