Kubernetes API
Managed Kubernetes clusters — create, scale, secure, back up and upgrade clusters. Authenticate with a personal access token carrying the kubernetes:read or kubernetes:write scope (see API Keys).
Cluster creation and deletion are asynchronous: the API answers 202 Accepted and the cluster moves through provisioning_step values (validate → network → gateway → peering → cluster → wait → access → ptr → backup → activate) until status is running. Poll GET /v1/kubernetes/clusters/{cluster_id}.
Endpoints
All paths are relative to https://api.wayscloud.services/v1/kubernetes. {id} is the cluster id, {pool} a node-pool name, {ip} an IPv4 address, {backup} a backup id.
Catalogue
| Method | Path | Description |
|---|---|---|
GET | /plans | List cluster and node plans |
GET | /regions | Regions where Kubernetes is available |
GET | /versions | Offered Kubernetes versions |
POST | /estimate | Monthly price estimate |
Clusters
| Method | Path | Description |
|---|---|---|
GET | /clusters | List clusters |
POST | /clusters | Create a cluster (202) |
GET | /clusters/{id} | Get a cluster |
DELETE | /clusters/{id} | Delete a cluster (202) |
GET | /clusters/{id}/history | Audit history |
Node pools
| Method | Path | Description |
|---|---|---|
POST | /clusters/{id}/node-pools | Add a node pool |
POST | /clusters/{id}/node-pools/{pool}/scale | Scale a node pool |
DELETE | /clusters/{id}/node-pools/{pool} | Delete a node pool |
Access
| Method | Path | Description |
|---|---|---|
GET | /clusters/{id}/kubeconfig | Admin kubeconfig (YAML) |
PUT | /clusters/{id}/api-access | Set the API allow-list |
Public IPs
| Method | Path | Description |
|---|---|---|
POST | /clusters/{id}/ips | Allocate a public IPv4 |
DELETE | /clusters/{id}/ips/{ip} | Release a public IPv4 |
PUT | /clusters/{id}/ips/{ip}/ptr | Set reverse DNS |
Backups
| Method | Path | Description |
|---|---|---|
GET | /clusters/{id}/backup-policy | Get backup policy |
PUT | /clusters/{id}/backup-policy | Set backup policy |
GET | /clusters/{id}/backups | List backups |
POST | /clusters/{id}/backups | Start a backup now (202) |
POST | /clusters/{id}/backups/{backup}/restore | Restore a backup (202) |
Upgrades
| Method | Path | Description |
|---|---|---|
GET | /clusters/{id}/upgrade | Available upgrades |
POST | /clusters/{id}/upgrade | Upgrade Kubernetes version |
Errors are returned as {"detail": {"code": "...", "message": "..."}}. Codes you will meet: not_found, not_ready (409), name_taken (409), invalid_plan, invalid_request, region_unavailable, ptr_not_forward_confirmed, capacity (503), backend_error (502).
GET /v1/kubernetes/plans
List cluster and node plans
Prices are per month in every supported currency; node plans also carry an hourly rate, which is what you are billed.
Query parameters: region (default no), kind (controlplane | node | ip | lb | storage | backup).
Example:
curl "https://api.wayscloud.services/v1/kubernetes/plans?region=no&kind=node" \
-H "Authorization: Bearer wayscloud_pat_abc12_YOUR_SECRET"Response:
{
"plans": [
{
"plan_code": "k8s-node-2c4g",
"kind": "node",
"name": "Node 2 vCPU / 4 GB",
"cpu_cores": 2,
"ram_mb": 4096,
"disk_gb": 30,
"prices": {"NOK": 249.0, "SEK": 249.0, "DKK": 179.0, "EUR": 29.0, "USD": 29.0},
"hourly": {"NOK": 0.3458, "SEK": 0.3458, "DKK": 0.2486, "EUR": 0.0403, "USD": 0.0403}
}
]
}GET /v1/kubernetes/regions
Regions where Kubernetes is available
{"regions": [{"code": "no", "name": "Norge", "city": "Oslo", "country_code": "NO", "kubernetes_available": true, "price_multiplier": 1.0}]}POST /v1/kubernetes/estimate
Monthly price estimate
Request Body:
| Field | Type | Description |
|---|---|---|
plan_code | string | Cluster plan (k8s-cluster-dev, k8s-cluster-prod) |
region | string | Region code |
node_pools | array | [{"name", "plan_code", "count"}] |
currency | string | NOK SEK DKK EUR USD |
Response:
{
"currency": "NOK",
"region_multiplier": 1.0,
"lines": [
{"item": "k8s-cluster-dev", "quantity": 1, "monthly": 199.0},
{"item": "k8s-node-2c4g", "quantity": 2, "monthly": 498.0}
],
"monthly_total": 697.0
}GET /v1/kubernetes/clusters
List clusters
Response:
| Field | Type | Description |
|---|---|---|
clusters | array | Cluster objects (see below) |
total | integer |
POST /v1/kubernetes/clusters
Create a cluster — requires kubernetes:write. Returns 202 with the cluster in provisioning.
Request Body:
| Field | Type | Description |
|---|---|---|
name | string | 2–63 chars, lowercase letters, digits, hyphens; unique per account |
region | string | Region code from /regions (default no) |
plan_code | string | k8s-cluster-dev (default) or k8s-cluster-prod |
version | string | From /versions (default 1.34) |
node_pools | array | 1–8 pools: {"name", "plan_code", "count" (1–16), "labels"?, "taints"?} |
api_ip_filter | array | CIDRs allowed to reach the Kubernetes API; empty = any source |
ssh_key_ids | array | Account SSH key ids to install on nodes (optional) |
Example:
curl -X POST https://api.wayscloud.services/v1/kubernetes/clusters \
-H "Authorization: Bearer wayscloud_pat_abc12_YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{
"name": "shop",
"region": "no",
"plan_code": "k8s-cluster-dev",
"version": "1.34",
"node_pools": [{"name": "default", "plan_code": "k8s-node-2c4g", "count": 2}],
"api_ip_filter": ["203.0.113.0/24"]
}'Response (202):
{
"id": "3f2b6c1e-8a4d-4f0e-9c21-7d5a1b2c3d4e",
"name": "shop",
"technical_name": "7f3a2c",
"region": "no",
"region_name": "Norge",
"region_city": "Oslo",
"plan_code": "k8s-cluster-dev",
"version": "1.34",
"status": "provisioning",
"status_message": null,
"provisioning_step": "validate",
"api_endpoint": "https://7f3a2c.k8s.no.wayscloud.services:7443",
"api_ip_filter": ["203.0.113.0/24"],
"oidc_enabled": false,
"network_cidr": "10.60.0.0/24",
"node_pools": [
{"id": "…", "name": "default", "plan_code": "k8s-node-2c4g", "count": 2, "status": "pending", "cpu_cores": 2, "ram_mb": 4096, "disk_gb": 30, "nodes_seen": 0}
],
"public_ips": [],
"created_at": "2026-09-16T10:12:00Z",
"running_since": null,
"trial_ends_at": "2026-09-30T10:12:00Z",
"delete_requested_at": null
}status values: provisioning, running, updating, upgrading, degraded, suspended, deleting, error.
GET /v1/kubernetes/clusters/
Get a cluster — the same object as above, with public_ips filled in once the cluster runs:
"public_ips": [
{"id": "…", "address": "81.27.105.110", "family": "IPv4", "kind": "gateway", "included": true, "ptr_record": null},
{"id": "…", "address": "212.147.234.88", "family": "IPv4", "kind": "floating", "included": false, "ptr_record": "7f3a2c.k8s.no.wayscloud.services"}
]kind is gateway (outbound address, included in the cluster fee), lb (created by a Service of type LoadBalancer) or floating (ordered through /ips).
DELETE /v1/kubernetes/clusters/
Delete a cluster — requires kubernetes:write. A final backup is kept for 30 days.
Request Body: {"confirm_name": "shop"} — must equal the cluster name.
Response (202): the cluster with status: "deleting". It disappears from the list once the provider confirms removal.
POST /v1/kubernetes/clusters/{cluster_id}/node-pools
Add a node pool
{"name": "workers-large", "plan_code": "k8s-node-4c16g", "count": 3, "labels": {"tier": "batch"}, "taints": [{"key": "batch", "value": "true", "effect": "NoSchedule"}]}POST /v1/kubernetes/clusters/{cluster_id}/node-pools/{pool_name}/scale
Scale a node pool — {"count": 4} (0–16). Scaling down drains nodes first.
DELETE /v1/kubernetes/clusters/{cluster_id}/node-pools/
Delete a node pool — a cluster keeps at least one pool.
GET /v1/kubernetes/clusters/{cluster_id}/kubeconfig
Admin kubeconfig — requires kubernetes:write. Returns application/x-yaml, not JSON. Treat it as a secret; every download is recorded in the cluster history.
curl https://api.wayscloud.services/v1/kubernetes/clusters/$CLUSTER/kubeconfig \
-H "Authorization: Bearer wayscloud_pat_abc12_YOUR_SECRET" -o kubeconfig.yaml
chmod 600 kubeconfig.yaml && KUBECONFIG=kubeconfig.yaml kubectl get nodesThe file points at https://{id}.k8s.{region}.wayscloud.services:7443.
PUT /v1/kubernetes/clusters/{cluster_id}/api-access
Set the API allow-list — {"cidrs": ["203.0.113.0/24", "198.51.100.7/32"]}; an empty list allows any source. Takes effect within a minute.
POST /v1/kubernetes/clusters/{cluster_id}/ips
Allocate a public IPv4 — billed per day. Use it in a Service annotation to pin a load balancer to the address.
DELETE /v1/kubernetes/clusters/{cluster_id}/ips/
Release a public IPv4 — only addresses of kind floating.
PUT /v1/kubernetes/clusters/{cluster_id}/ips/{address}/ptr
Set reverse DNS — {"ptr_record": "mail.example.com"}. The name must already resolve to the address (ptr_not_forward_confirmed otherwise). Not available on the outbound gateway address.
GET /v1/kubernetes/clusters/{cluster_id}/backup-policy
{"enabled": true, "schedule_cron": "0 3 * * *", "retention_days": 14, "include_volumes": true, "namespaces": null, "active": true, "updated_at": "…"}PUT /v1/kubernetes/clusters/{cluster_id}/backup-policy
Same fields as the response; retention_days 1–365, schedule_cron is a 5-field cron expression in the cluster's local time, namespaces null means every namespace you created.
GET /v1/kubernetes/clusters/{cluster_id}/backups
{"backups": [{"id": "…", "kind": "scheduled", "name": "daily-20260916030000", "status": "Completed", "started_at": "…", "completed_at": "…", "expires_at": "…", "items_count": 42, "error": null}]}POST /v1/kubernetes/clusters/{cluster_id}/backups
Start a backup now — {"namespaces": ["shop"]} or {} for all. Returns 202 {"name": "manual-20260916-101200", "status": "InProgress"}.
POST /v1/kubernetes/clusters/{cluster_id}/backups/{backup_id}/restore
Restore a backup into the same cluster — {"namespaces": ["shop"]} optional. Only Completed backups can be restored. Returns 202 {"restore": "restore-…", "status": "InProgress"}.
GET /v1/kubernetes/clusters/{cluster_id}/upgrade
Available upgrades — {"versions": ["1.35"]}.
POST /v1/kubernetes/clusters/{cluster_id}/upgrade
Upgrade Kubernetes version — {"version": "1.35"}. A backup is taken first; status is upgrading until done.
SDK and CLI
from wayscloud import WaysCloudClient
c = WaysCloudClient(token="wayscloud_pat_...")
cluster = c.kubernetes.create("shop", [{"name": "default", "plan_code": "k8s-node-2c4g", "count": 2}])
cluster = c.kubernetes.wait(cluster["id"]) # blocks until running
open("kubeconfig.yaml", "w").write(c.kubernetes.kubeconfig(cluster["id"]))cloud k8s create shop --pool k8s-node-2c4g:2 --wait
cloud k8s kubeconfig <cluster-id> -o ~/.kube/shop.yamlSee Python SDK and CLI.