# Kubernetes

Managed Kubernetes clusters whose nodes are VMs in your account: create, scale, upgrade, stop, start and delete them, and download a kubeconfig.

Source: https://docs.pantechdynamics.com/api/kubernetes

A cluster's nodes, control and worker, are VMs of one `node_plan` in your account, each billed as a VM of that plan; there is no separate control-plane fee. Pick a version from `GET /kubernetes-versions` in the zone you want. In a VPC zone the nodes go in a [subnet](https://docs.pantechdynamics.com/api/networks); in a standard zone leave `subnet_id` out. `control_nodes` is 1, or 3 where the version list's `ha_zone_ids` offers it.

Every change returns an [operation](https://docs.pantechdynamics.com/api/operations) to follow. Scale to 1 to 10 workers with `PATCH`, and upgrade one step at a time to one of the cluster's `available_upgrades`; upgrades never happen on their own and never go down. Download the kubeconfig once the cluster is `running`: it is a `POST` that needs a `write` key, because it grants cluster-admin. A `503 KUBERNETES_UNAVAILABLE` means managed Kubernetes is not open on the platform yet. See the [Kubernetes guide](https://docs.pantechdynamics.com/kubernetes/overview).

Endpoints:

- `GET /public/v1/kubernetes-versions`: List Kubernetes versions
- `GET /public/v1/kubernetes-clusters`: List Kubernetes clusters
- `POST /public/v1/kubernetes-clusters`: Create a Kubernetes cluster
- `GET /public/v1/kubernetes-clusters/{cluster_id}`: Get a Kubernetes cluster
- `PATCH /public/v1/kubernetes-clusters/{cluster_id}`: Change a Kubernetes cluster
- `DELETE /public/v1/kubernetes-clusters/{cluster_id}`: Delete a Kubernetes cluster
- `POST /public/v1/kubernetes-clusters/{cluster_id}/upgrade`: Upgrade a Kubernetes cluster
- `POST /public/v1/kubernetes-clusters/{cluster_id}/resize-plan`: Resize worker node compute plan
- `POST /public/v1/kubernetes-clusters/{cluster_id}/start`: Start a Kubernetes cluster
- `POST /public/v1/kubernetes-clusters/{cluster_id}/upgrade-network`: Upgrade Kubernetes cluster networking
- `POST /public/v1/kubernetes-clusters/{cluster_id}/stop`: Stop a Kubernetes cluster
- `POST /public/v1/kubernetes-clusters/{cluster_id}/kubeconfig`: Download a kubeconfig
- `GET /public/v1/kubernetes-clusters/{cluster_id}/nodes`: List a cluster's nodes
- `GET /public/v1/kubernetes-clusters/{cluster_id}/workloads`: Get a cluster's services and pods
- `POST /public/v1/kubernetes-clusters/{cluster_id}/apply-manifest`: Apply a manifest to a cluster
- `GET /public/v1/kubernetes-clusters/{cluster_id}/live-manifests`: Export a cluster's workloads as YAML
- `POST /public/v1/kubernetes-clusters/{cluster_id}/expose-service`: Expose a Kubernetes service
- `POST /public/v1/kubernetes-clusters/{cluster_id}/sync-routing`: Sync a cluster's public routing
- `GET /public/v1/kubernetes-clusters/{cluster_id}/node-pools`: List node pools
- `POST /public/v1/kubernetes-clusters/{cluster_id}/node-pools`: Create a node pool
- `PATCH /public/v1/kubernetes-clusters/{cluster_id}/node-pools/{pool_id}`: Scale a node pool
- `DELETE /public/v1/kubernetes-clusters/{cluster_id}/node-pools/{pool_id}`: Delete a node pool
- `GET /public/v1/kubernetes-clusters/{cluster_id}/addons`: List cluster add-ons
- `POST /public/v1/kubernetes-clusters/{cluster_id}/addons/{addon_name}/toggle`: Toggle a cluster add-on
- `POST /public/v1/kubernetes-clusters/{cluster_id}/endpoint-access`: Configure API server endpoint access

## The Kubernetes cluster object



- `id` (string, required): e.g. `k8s_...`.
- `name` (string, required)
- `zone_id` (string, required)
- `kubernetes_version_id` (string, required)
- `kubernetes_version` (string, required)
- `network_id` (string or null, required): The private network (VPC) the nodes are in; null in a standard zone.
- `subnet_id` (string or null, required)
- `node_plan_id` (string, required)
- `node` (KubernetesNode, required)
  - `vcpu` (integer, required)
  - `memory_mb` (integer, required)
  - `disk_gb` (integer, required)
- `control_nodes` (integer, required)
- `workers` (integer, required)
- `nodes` (integer, required): control_nodes plus workers: the VMs billed.
- `desired_state` (string, required): One of `running`, `stopped`, `deleted`.
- `observed_state` (string, required): `updating` while a scale or upgrade runs. One of `pending`, `provisioning`, `running`, `updating`, `stopping`, `stopped`, `retired`, `deleted`, `failed`, `unknown`.
- `in_sync` (boolean, required): False while a scale or upgrade has not finished.
- `failure_code` (string or null, required)
- `failure_reason` (string or null, required)
- `available_upgrades` (array of KubernetesVersion, required): Versions this cluster can be upgraded to now.
  - `id` (string, required): e.g. `k8sv_...`.
  - `zone_id` (string, required)
  - `version` (string, required)
  - `status` (string, required): `withdrawn` versions take no new clusters. One of `available`, `withdrawn`.
  - `min_cpu` (integer, required): The fewest vCPUs a node plan needs for this version.
  - `min_memory_mb` (integer, required): The least memory, in MB, a node plan needs for this version.
- `autoscaling` (KubernetesAutoscaling, required): The platform's autoscaler: about once a minute it adds a worker when a pod that a worker could run is waiting for room, and removes the newest worker once it has run nothing but DaemonSet pods for 10 minutes, one step at a time and always within min_workers and max_workers. You pay for the workers that run.
  - `enabled` (boolean, required): The cluster autoscaler adds and removes workers within the range as pods need them.
  - `min_workers` (integer, required): 1 to 10 when enabled; 0 when not.
  - `max_workers` (integer, required): min_workers to 10 when enabled; 0 when not. Billing and your account limits are checked for the maximum.
- `api_allowed_cidrs` (array of string, required): Sources allowed to reach the API server; empty is any address.
- `endpoint` (string or null, required): The API server's URL, once the cluster has one: the public address when the endpoint is public or dual and one is routed, else the private one.
- `public_ip` (string or null): The public address the API server is reachable on; null until one is routed, and for a private endpoint.
- `public_port` (integer or null): The public port the API server is reachable on; null until one is routed, and for a private endpoint.
- `volume_storage_gb` (integer, required): Storage your workloads' persistent volume claims use, billed with the nodes' disks.
- `kind` (string, required): Cluster architecture kind, e.g. `appliance`.
- `cni_plugin` (string, required): CNI backend plugin (`flannel`, `wireguard`, or `calico`).
- `pod_cidr` (string, required): IPv4 CIDR range allocated for cluster pods.
- `service_cidr` (string, required): IPv4 CIDR range allocated for cluster services.
- `endpoint_access` (string, required): API server endpoint network access mode (`public`, `private`, or `dual`). One of `public`, `private`, `dual`.
- `created_at` (string or null, required)
- `updated_at` (string or null, required)
## The Kubernetes version object



- `id` (string, required): e.g. `k8sv_...`.
- `zone_id` (string, required)
- `version` (string, required)
- `status` (string, required): `withdrawn` versions take no new clusters. One of `available`, `withdrawn`.
- `min_cpu` (integer, required): The fewest vCPUs a node plan needs for this version.
- `min_memory_mb` (integer, required): The least memory, in MB, a node plan needs for this version.
## List Kubernetes versions

```http
GET /public/v1/kubernetes-versions
```

The Kubernetes versions you can create clusters with, newest first, each in one zone. `ha_zone_ids` are the zones where a highly available control plane (3 control nodes) can be created now. A version is withdrawn from new clusters before its upstream end of life; clusters already on it keep running it.

Scope: `read`.

Query parameters:

- `zone_id` (string): Only versions in this zone.

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-versions" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "k8sv_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "zone_id": "af-abj-2",
      "version": "1.31.2",
      "status": "available",
      "min_cpu": 2,
      "min_memory_mb": 2048
    }
  ],
  "ha_zone_ids": [
    "af-abj-2"
  ]
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
## List Kubernetes clusters

```http
GET /public/v1/kubernetes-clusters
```

Your Kubernetes clusters, newest first. Deleted and failed clusters are left out.

Scope: `read`.

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "prod-web",
      "zone_id": "af-abj-2",
      "kubernetes_version_id": "k8sv_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "kubernetes_version": "1.31.2",
      "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "node_plan_id": "plan_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "node": {
        "vcpu": 2,
        "memory_mb": 4096,
        "disk_gb": 80
      },
      "control_nodes": 1,
      "workers": 2,
      "nodes": 3,
      "desired_state": "running",
      "observed_state": "running",
      "in_sync": true,
      "failure_code": null,
      "failure_reason": null,
      "available_upgrades": [
        {
          "id": "k8sv_07hm4y0ur8n3w9s5z2d6e1aq9x",
          "zone_id": "af-abj-2",
          "version": "1.32.0",
          "status": "available",
          "min_cpu": 2,
          "min_memory_mb": 2048
        }
      ],
      "autoscaling": {
        "enabled": true,
        "min_workers": 2,
        "max_workers": 5
      },
      "api_allowed_cidrs": [
        "api-allowed-cidrs"
      ],
      "endpoint": "endpoint",
      "public_ip": "203.0.113.20",
      "public_port": 6443,
      "volume_storage_gb": 1,
      "kind": "appliance",
      "cni_plugin": "flannel",
      "pod_cidr": "10.42.0.0/16",
      "service_cidr": "10.43.0.0/16",
      "endpoint_access": "public",
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": null
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
## Create a Kubernetes cluster

```http
POST /public/v1/kubernetes-clusters
```

Creates a managed Kubernetes cluster. Every node, control and worker, is a VM of `node_plan` in your account and is billed as one; there is no separate control-plane fee. In a VPC zone the nodes go in `subnet_id`; in a standard zone leave `subnet_id` out. `control_nodes` is 1, or 3 where the zone offers a highly available control plane (see `ha_zone_ids` on the versions list). Persistent volumes work out of the box. Download the kubeconfig once the cluster is `running`.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice. The cluster's id is the operation's `resource_id`.

Scope: `write`. Send an `Idempotency-Key` header.

Body:

- `name` (string, required): 1 to 40 lowercase letters, digits and hyphens, starting with a letter.
- `zone_id` (string, required)
- `kubernetes_version_id` (string, required): From the Kubernetes versions list, in `zone_id`.
- `subnet_id` (string): Required in a VPC zone: the subnet the nodes go in. Leave out in a standard zone.
- `node_plan` (string, required): The plan slug every node uses, from the plans list. In a zone where every node has one fixed size (af-abj-2: 2 vCPU, 4 GB), the plan must be that size: `422 KUBERNETES_PLAN_NOT_IN_ZONE` on `node_plan`.
- `control_nodes` (integer): 1, or 3 for a highly available control plane where the zone offers it. One of `1`, `3`. Defaults to `1`.
- `workers` (integer): Worker nodes, 1 to 10. Required unless `autoscaling` is enabled, when it defaults to `min_workers` and must lie within the range. From 1 to 10.
- `autoscaling` (KubernetesAutoscaling): The platform's autoscaler: about once a minute it adds a worker when a pod that a worker could run is waiting for room, and removes the newest worker once it has run nothing but DaemonSet pods for 10 minutes, one step at a time and always within min_workers and max_workers. You pay for the workers that run.
  - `enabled` (boolean, required): The cluster autoscaler adds and removes workers within the range as pods need them.
  - `min_workers` (integer, required): 1 to 10 when enabled; 0 when not.
  - `max_workers` (integer, required): min_workers to 10 when enabled; 0 when not. Billing and your account limits are checked for the maximum.
- `api_allowed_cidrs` (array of string): VPC zone only: the IPv4 CIDRs allowed to reach the API server (at most 20). Leave out for any address.
- `kind` (string): Cluster architecture. Only `appliance` (the default) is offered: nodes are VMs in your account running k3s. Any other value is `422 INVALID_KUBERNETES_KIND`. One of `appliance`.
- `cni_plugin` (string): CNI backend plugin (`flannel`, `wireguard`, or `calico`). Defaults to `flannel`.
- `pod_cidr` (string): IPv4 CIDR range allocated for cluster pods. Defaults to `10.42.0.0/16`.
- `service_cidr` (string): IPv4 CIDR range allocated for cluster services. Defaults to `10.43.0.0/16`.
- `endpoint_access` (string): Who can reach the API server. `private` (the default) is reachable only from inside the VPC. `public` and `dual` give the API server a public address of its own, billed as a VPC public IP for every hour it is held. One of `private`, `public`, `dual`. Defaults to `private`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prod-web",
    "zone_id": "af-abj-2",
    "kubernetes_version_id": "k8sv_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
    "node_plan": "s-2vcpu-4gb",
    "control_nodes": 1
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `402`: `INSUFFICIENT_CREDIT`: your credit does not cover the first payment for every node. Top up or add a card, then try again.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `409`: `KUBERNETES_CLUSTER_NAME_TAKEN`: you already have a cluster with this name.
`KUBERNETES_CLUSTER_LIMIT`: your organization has reached its cluster limit. Delete a cluster, or contact support to raise it.
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: a field is invalid. Each entry of `errors` names the `field` and its `code`:
`name`, `zone_id`, `kubernetes_version_id`, `node_plan` or `workers` `REQUIRED`: send them.
`name` `INVALID_KUBERNETES_CLUSTER_NAME`: 1 to 40 lowercase letters, digits and hyphens, starting with a letter.
`zone_id` `INVALID_KUBERNETES_ZONE`: a zone your organization can use.
`kubernetes_version_id` `INVALID_KUBERNETES_VERSION`: a version offered in this zone.
`node_plan` `INVALID_KUBERNETES_NODE_PLAN`: an active plan with at least the version's minimum vCPU and memory.
`workers` `INVALID_KUBERNETES_WORKER_COUNT`: 1 to 10.
`control_nodes` `INVALID_KUBERNETES_CONTROL_NODES` or `KUBERNETES_HA_NOT_AVAILABLE`: 1, or 3 where offered.
`autoscaling` `INVALID_KUBERNETES_AUTOSCALING`: min_workers and max_workers 1 to 10, min no more than max, and workers within them.
`api_allowed_cidrs` `INVALID_API_ALLOWED_CIDRS` (at most 20 IPv4 CIDRs) or `API_ALLOWED_CIDRS_VPC_ONLY`.
`subnet_id` `KUBERNETES_SUBNET_REQUIRED`, `KUBERNETES_SUBNET_NOT_ALLOWED` or `KUBERNETES_SUBNET_NOT_FOUND`: an active subnet in a VPC zone, and none in a standard zone.
`UNKNOWN_FIELD`: the body has a field this endpoint does not take (named in `field`).
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Get a Kubernetes cluster

```http
GET /public/v1/kubernetes-clusters/{cluster_id}
```

One cluster. `in_sync` is false while a change you made has not reached it yet; `available_upgrades` lists the versions it can move to now.

Scope: `read`.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "name": "prod-web",
  "zone_id": "af-abj-2",
  "kubernetes_version_id": "k8sv_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "kubernetes_version": "1.31.2",
  "network_id": "net_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "subnet_id": "snet_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "node_plan_id": "plan_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "node": {
    "vcpu": 2,
    "memory_mb": 4096,
    "disk_gb": 80
  },
  "control_nodes": 1,
  "workers": 2,
  "nodes": 3,
  "desired_state": "running",
  "observed_state": "running",
  "in_sync": true,
  "failure_code": null,
  "failure_reason": null,
  "available_upgrades": [
    {
      "id": "k8sv_07hm4y0ur8n3w9s5z2d6e1aq9x",
      "zone_id": "af-abj-2",
      "version": "1.32.0",
      "status": "available",
      "min_cpu": 2,
      "min_memory_mb": 2048
    }
  ],
  "autoscaling": {
    "enabled": true,
    "min_workers": 2,
    "max_workers": 5
  },
  "api_allowed_cidrs": [
    "api-allowed-cidrs"
  ],
  "endpoint": "endpoint",
  "public_ip": "203.0.113.20",
  "public_port": 6443,
  "volume_storage_gb": 1,
  "kind": "appliance",
  "cni_plugin": "flannel",
  "pod_cidr": "10.42.0.0/16",
  "service_cidr": "10.43.0.0/16",
  "endpoint_access": "public",
  "created_at": "2026-10-03T09:30:00Z",
  "updated_at": "2026-10-03T09:30:00Z"
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
## Change a Kubernetes cluster

```http
PATCH /public/v1/kubernetes-clusters/{cluster_id}
```

Sets a fixed number of worker nodes (1 to 10), turns the cluster autoscaler on or off and sets its range, or replaces the list of addresses allowed to reach the API server (VPC zone). Send any of them; the rest stay as they are. The cluster must be running. Added workers, and an autoscaler maximum above today's count, are checked against your credit and account limits; workers are billed as VMs of the node plan while they run. A change that changes nothing returns an empty `operation_id`.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Body:

- `workers` (integer): A fixed worker count, 1 to 10. Not with autoscaling enabled: the autoscaler sets it. From 1 to 10.
- `autoscaling` (KubernetesAutoscaling): The platform's autoscaler: about once a minute it adds a worker when a pod that a worker could run is waiting for room, and removes the newest worker once it has run nothing but DaemonSet pods for 10 minutes, one step at a time and always within min_workers and max_workers. You pay for the workers that run.
  - `enabled` (boolean, required): The cluster autoscaler adds and removes workers within the range as pods need them.
  - `min_workers` (integer, required): 1 to 10 when enabled; 0 when not.
  - `max_workers` (integer, required): min_workers to 10 when enabled; 0 when not. Billing and your account limits are checked for the maximum.
- `api_allowed_cidrs` (array of string): VPC zone only: the whole new list of IPv4 CIDRs allowed to reach the API server. Send `[]` to allow any address.

Example:

```sh
curl -X PATCH "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `402`: `INSUFFICIENT_CREDIT`: your credit does not cover the added nodes.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
`KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
`KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: `workers` `REQUIRED` (send at least one field) or `INVALID_KUBERNETES_WORKER_COUNT` (1 to 10); `autoscaling` `INVALID_KUBERNETES_AUTOSCALING`; `api_allowed_cidrs` `INVALID_API_ALLOWED_CIDRS` or `API_ALLOWED_CIDRS_VPC_ONLY`.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Delete a Kubernetes cluster

```http
DELETE /public/v1/kubernetes-clusters/{cluster_id}
```

Deletes the cluster. It stops at once, disappears from your lists and stops billing; its nodes and disks are destroyed 12 hours later and cannot be recovered. Volumes your workloads created through persistent volume claims are deleted with it. Deleting a cluster already being deleted returns an empty `operation_id`.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

**Scope:** `write`. Destructive: an AI app's key also needs `delete`.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl -X DELETE "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Upgrade a Kubernetes cluster

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/upgrade
```

Moves the cluster to a newer Kubernetes version: one of its `available_upgrades` (the next patch, or the next minor version). Upgrades never happen on their own and never go down. The cluster must be running; nodes are replaced one at a time.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Body:

- `kubernetes_version_id` (string, required): One of the cluster's `available_upgrades`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/upgrade" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "kubernetes_version_id": "k8sv_07hm4y0ur8n3w9s5z2d6e1aq9x"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
`KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
`KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: `kubernetes_version_id` `REQUIRED`, `INVALID_KUBERNETES_VERSION` or `KUBERNETES_UPGRADE_NOT_ALLOWED` (pick one of `available_upgrades`).
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Resize worker node compute plan

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/resize-plan
```

Resizes the compute plan of worker nodes in the cluster via zero-downtime rolling replacement.
A new worker node with the updated plan is provisioned, joins the cluster, workloads migrate, and the old node is retired.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Body:

- `node_plan` (string, required): The new node plan slug, from the plans list. In a zone with one fixed node size the plan must be that size (`422 KUBERNETES_PLAN_NOT_IN_ZONE`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/resize-plan" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "node_plan": "s-2vcpu-4gb"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
`KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
`KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: `node_plan` `REQUIRED` or `INVALID_KUBERNETES_NODE_PLAN`.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Start a Kubernetes cluster

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/start
```

Starts a stopped cluster. Its nodes are billed for compute again once it runs.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/start" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
`KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Upgrade Kubernetes cluster networking

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/upgrade-network
```

Reconfigures the CNI plugin (`flannel`, `wireguard`, `calico`), Pod CIDR, Service CIDR, or API allowed CIDR ranges for the cluster.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Body:

- `cni_plugin` (string): The new CNI plugin (`flannel`, `wireguard`, or `calico`).
- `pod_cidr` (string): The new IPv4 CIDR range allocated for cluster pods.
- `service_cidr` (string): The new IPv4 CIDR range allocated for cluster services.
- `api_allowed_cidrs` (array of string): The new allowed IPv4 CIDRs reaching the API server.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/upgrade-network" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
`INVALID_REQUEST_BODY`: the body is not a single valid JSON object. Send one JSON object with the fields documented for this endpoint.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`: start the cluster first.
`KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
`KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `413`: `REQUEST_TOO_LARGE`: the body is larger than 64 KiB. Send a smaller request.
- `422`: `VALIDATION_FAILED`: `INVALID_KUBERNETES_CNI_PLUGIN` or `INVALID_KUBERNETES_CIDR`.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Stop a Kubernetes cluster

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/stop
```

Stops a running cluster. While stopped only its disks are billed.

Returns `202 Accepted` with an `operation_id` (also in the `Location` header). The change happens in the background: poll `GET /public/v1/operations/{operation_id}`, or stream `/events`, until `status` is `succeeded` or `failed`; a failed operation carries a stable `failure.code` and a readable `failure.reason`.

Send an `Idempotency-Key`. A retry with the same key returns the original operation instead of making the change twice.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/stop" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBERNETES_CLUSTER_BUSY`: the cluster is already changing. Wait until it is running or stopped.
`KUBERNETES_CLUSTER_NOT_CHANGEABLE`: the cluster cannot make this change now (stopped, being deleted, or failed).
`CONCURRENT_UPDATE`: the cluster changed while this request was being processed. Retry the request; if it repeats, read the cluster first.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
`PROVISIONING_UNAVAILABLE`: the provisioning service is temporarily unavailable. Wait a few seconds, then retry with the same `Idempotency-Key`.
## Download a kubeconfig

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/kubeconfig
```

The cluster's admin kubeconfig, for `kubectl`. It holds a long-lived cluster-admin credential: store it like a password. It is fetched fresh on each call and is not kept by the platform. Available while the cluster is running. It is a `POST` that needs a `write` key so that a read-only key can never fetch a cluster-admin credential; the response is `Cache-Control: no-store`.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/kubeconfig" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `200`:

```json
{
  "kubeconfig": "apiVersion: v1\nkind: Config\nclusters:\n- name: prod-web\n  cluster:\n    server: https://…:6443\n    certificate-authority-data: …\n…"
}
```

Errors:

- `400`: `IDEMPOTENCY_KEY_REQUIRED`: the `Idempotency-Key` header is missing. Send a new unique value, such as a UUID, with every write.
`INVALID_IDEMPOTENCY_KEY`: the `Idempotency-Key` header is longer than 255 characters. Use a shorter value.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`: no cluster with this id in your organization. Check the id.
`RESOURCE_NOT_FOUND`: the id is not a cluster id (`k8s_…`).
- `409`: `KUBECONFIG_UNAVAILABLE`: the cluster is not running yet. Wait until it is, then try again.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: `SERVICE_UNAVAILABLE`: request quota storage is unavailable. Wait a few seconds, then retry.
`KUBERNETES_UNAVAILABLE`: managed Kubernetes is not open on this platform yet.
## List a cluster's nodes

```http
GET /public/v1/kubernetes-clusters/{cluster_id}/nodes
```

The cluster's nodes as Kubernetes reports them right now, read from the cluster's own API server. `vcpu` and `memory_mb` are each node's capacity; `disk_gb` is the cluster's node plan.

Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING` (start the cluster first); `503 KUBERNETES_API_UNREACHABLE` (the API server did not answer: it may still be starting, or its endpoint may be private; retry).

Scope: `read`.

Path parameters:

- `cluster_id`

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/nodes" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "nodes": [
    {
      "id": "id",
      "name": "web-1",
      "role": "control",
      "status": "Ready",
      "ip_address": "ip-address",
      "vcpu": 2,
      "memory_mb": 4096,
      "disk_gb": 80,
      "kubelet_version": "v1.31.2+k3s1",
      "created_at": "2026-10-03T09:30:00Z"
    }
  ]
}
```

Errors:

- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`.
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`.
- `503`: `KUBERNETES_API_UNREACHABLE`: retry.
## Get a cluster's services and pods

```http
GET /public/v1/kubernetes-clusters/{cluster_id}/workloads
```

Every Service and pod in the cluster, in every namespace, read live from its API server. A cluster you have not deployed anything to shows only what Kubernetes itself runs. A service you exposed (see *Expose a Kubernetes service*) has `public_port` and `external_url`, the address it is reached on from the internet.

Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `503 KUBERNETES_API_UNREACHABLE` (retry).

Scope: `read`.

Path parameters:

- `cluster_id`

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/workloads" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "public_ip": "public-ip",
  "api_port": 1,
  "services": [
    {
      "name": "web-1",
      "namespace": "namespace",
      "type": "ClusterIP",
      "cluster_ip": "cluster-ip",
      "external_ip": "external-ip",
      "ports": [
        {
          "name": "web-1",
          "protocol": "protocol",
          "port": 22,
          "target_port": 1,
          "node_port": 1
        }
      ],
      "public_port": 1,
      "external_url": "http://203.0.113.10:8088",
      "created_at": "2026-10-03T09:30:00Z"
    }
  ],
  "pods": [
    {
      "name": "web-1",
      "namespace": "namespace",
      "status": "Running",
      "ready": "1/1",
      "restarts": 1,
      "node_name": "node-name",
      "pod_ip": "pod-ip",
      "age": "3h",
      "created_at": "2026-10-03T09:30:00Z"
    }
  ]
}
```

Errors:

- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`.
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`.
- `503`: `KUBERNETES_API_UNREACHABLE`: retry.
## Apply a manifest to a cluster

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/apply-manifest
```

Applies a YAML manifest (one or more documents, up to 512 KB and 100 objects) to the cluster with server-side apply, as `kubectl apply --server-side --force-conflicts` would, in order. Namespaced objects without a namespace go in `namespace` (default `default`). Applying the same manifest again changes nothing, so a retry is safe. If the cluster refuses an object, the objects before it stay applied and the error names the one it refused.

Errors: `422 VALIDATION_FAILED` with `errors[].field = manifest` and code `INVALID_KUBERNETES_MANIFEST`, whose `message` is the cluster's own reason (for example a missing field); `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `503 KUBERNETES_API_UNREACHABLE` (retry).

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`

Body:

- `manifest` (string, required): At most 524288 characters.
- `namespace` (string): Where namespaced objects without one go; default `default`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/apply-manifest" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": "manifest"
  }'
```

Response `200`:

```json
{
  "applied": true,
  "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "output": "deployment.apps/web created",
  "resources": [
    {
      "kind": "create_instance",
      "name": "web-1",
      "namespace": "namespace",
      "status": "created"
    }
  ]
}
```

Errors:

- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`.
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`.
- `422`: `VALIDATION_FAILED`: `INVALID_KUBERNETES_MANIFEST` on `manifest`, or `REQUIRED`.
- `503`: `KUBERNETES_API_UNREACHABLE`: retry.
## Export a cluster's workloads as YAML

```http
GET /public/v1/kubernetes-clusters/{cluster_id}/live-manifests
```

Your objects as they run in the cluster now, as one multi-document YAML stream you can edit and apply back: Namespaces, ConfigMaps, Services, Deployments, StatefulSets, DaemonSets, CronJobs and Ingresses outside the `kube-*` namespaces. Fields the cluster fills in (status, resource versions, cluster IPs) are left out. Objects a controller made (a Deployment's ReplicaSets and pods) are left out, since their owner is exported. Secrets are never exported.

Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `503 KUBERNETES_API_UNREACHABLE` (retry).

Scope: `read`.

Path parameters:

- `cluster_id`

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/live-manifests" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "manifest": "manifest",
  "resource_count": 1,
  "retrieved_at": "2026-10-03T09:30:00Z"
}
```

Errors:

- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`.
- `409`: `KUBERNETES_CLUSTER_NOT_RUNNING`.
- `503`: `KUBERNETES_API_UNREACHABLE`: retry.
## Expose a Kubernetes service

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/expose-service
```

Routes `public_port` on the cluster's public address to a Service. The Service's port becomes a NodePort (`node_port`, or the one it has, or one the cluster picks), the Service records the public port in its `pantechdynamics.com/public-port` annotation, and routing is synced. The cluster is the record of what is exposed: removing the annotation with kubectl and syncing routing takes the route away. Your subnet's firewall rules must allow the public port.

Public routing needs a cluster in a VPC subnet: it uses the VPC's public address. Port 22 and the cluster's API port are reserved.

Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `404 KUBERNETES_SERVICE_NOT_FOUND` (apply the Service first); `409 PORT_IN_USE` (another service, cluster or resource uses that public port; choose another); `409 KUBERNETES_ROUTING_UNAVAILABLE` (a cluster with no VPC address); `409 KUBERNETES_CLUSTER_NOT_RUNNING`; `422 VALIDATION_FAILED` (`service_name`, `public_port` or `node_port`; or `INVALID_KUBERNETES_MANIFEST` when the cluster refused the change); `503 KUBERNETES_API_UNREACHABLE` (retry).

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`

Body:

- `service_name` (string, required)
- `namespace` (string): Default `default`.
- `public_port` (integer, required): From 1 to 65535.
- `node_port` (integer): Leave out to keep the Service's own NodePort, or let the cluster choose one. From 30000 to 32767.
- `protocol` (string): One of `TCP`, `UDP`. Defaults to `TCP`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/expose-service" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "service_name": "service-name",
    "public_port": 1
  }'
```

Response `200`:

```json
{
  "success": true,
  "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "service_name": "service-name",
  "namespace": "namespace",
  "public_ip": "public-ip",
  "public_port": 1,
  "node_port": 1,
  "protocol": "protocol",
  "external_url": "http://203.0.113.10:8088",
  "status": "active"
}
```

Errors:

- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND` or `KUBERNETES_SERVICE_NOT_FOUND`.
- `409`: `PORT_IN_USE`, `KUBERNETES_ROUTING_UNAVAILABLE` or `KUBERNETES_CLUSTER_NOT_RUNNING`.
- `422`: `VALIDATION_FAILED`.
- `503`: `KUBERNETES_API_UNREACHABLE`: retry.
## Sync a cluster's public routing

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/sync-routing
```

Reconciles the port forwards on the cluster's public address with what the cluster exposes now: its API server (unless its endpoint is private), on the cluster's own API port, and every Service carrying the `pantechdynamics.com/public-port` annotation. Routes to services no longer exposed are removed. Only the cluster's own routes are ever changed: other clusters and your own port forwards on the same address are left alone.

Every cluster on a shared address gets its own API port: the first 6443, the next 6444, and so on. A new cluster's API port is routed automatically once it is running; call this after exposing or unexposing services with kubectl. If the API server does not answer, only its own route is ensured and no service route is removed.

Errors: `404 KUBERNETES_CLUSTER_NOT_FOUND`; `409 PORT_IN_USE`; `409 KUBERNETES_ROUTING_UNAVAILABLE` (a cluster with no VPC address); `409 KUBERNETES_CLUSTER_NOT_RUNNING`.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/sync-routing" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `200`:

```json
{
  "success": true,
  "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "public_ip": "public-ip",
  "api_port": 1,
  "synced_ports": [
    {
      "purpose": "kubernetes-api",
      "public_port": 1,
      "private_port": 1,
      "protocol": "protocol",
      "target": "target",
      "status": "active"
    }
  ],
  "message": "message"
}
```

Errors:

- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: `KUBERNETES_CLUSTER_NOT_FOUND`.
- `409`: `PORT_IN_USE`, `KUBERNETES_ROUTING_UNAVAILABLE` or `KUBERNETES_CLUSTER_NOT_RUNNING`.
## List node pools

```http
GET /public/v1/kubernetes-clusters/{cluster_id}/node-pools
```

Lists the cluster's node pools.

Scope: `read`.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/node-pools" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "status": "provisioning",
      "observed_workers": 1,
      "id": "id",
      "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "web-1",
      "node_plan_id": "plan_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "workers": 1,
      "autoscaling_enabled": true,
      "min_workers": 1,
      "max_workers": 1,
      "labels": {},
      "taints": [
        {
          "key": "key",
          "value": "value",
          "effect": "NoSchedule"
        }
      ],
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": "eyJhdCI6IjIwMjYtMTAtMDNUMDk6MzA6MDBaIiwiaWQiOiJ2bV8wNmdrIn0"
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster not found.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
## Create a node pool

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/node-pools
```

Adds a node pool: workers of their own plan, joined with the pool's labels, the label `pantech.io/node-pool=<name>` and the pool's taints, so workloads choose it with a nodeSelector and tolerations. Each worker is billed as a VM of the pool's plan once it has joined (`observed_workers`). With autoscaling on, the platform adds a worker when a pod the pool could run is waiting for room, and removes the newest one after 10 idle minutes. A pool's name is unique in its cluster: sending the same pool again is answered as a replay, another pool under a taken name is `409 NODE_POOL_NAME_TAKEN`. In a zone with one fixed node size (af-abj-2), the plan must be that size (`422 KUBERNETES_PLAN_NOT_IN_ZONE`).

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Body:

- `name` (string, required): Unique name for the node pool.
- `plan_id` (string, required): Compute plan ID or slug for the worker VMs.
- `workers` (integer): Worker nodes, 1 to 10. Required unless autoscaling_enabled, when it defaults to min_workers.
- `autoscaling_enabled` (boolean): Defaults to `false`.
- `min_workers` (integer): With autoscaling_enabled: fewest workers, 1 to 10.
- `max_workers` (integer): With autoscaling_enabled: most workers, min_workers to 10. Your credit and limits are checked for this many.
- `labels` (object)
- `taints` (array of KubernetesNodePoolTaint)
  - `key` (string, required)
  - `value` (string, required)
  - `effect` (string, required): One of `NoSchedule`, `PreferNoSchedule`, `NoExecute`.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/node-pools" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "cpu-workers",
    "plan_id": "standard-2"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: Bad request.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster not found.
- `409`: Cluster busy or duplicate node pool name.
- `422`: Validation error.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
## Scale a node pool

```http
PATCH /public/v1/kubernetes-clusters/{cluster_id}/node-pools/{pool_id}
```

Changes a node pool's worker count and autoscaling. The body is the pool's whole size.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).
- `pool_id`: The node pool's id (`k8spool_…`).

Body:

- `workers` (integer): Worker nodes, 1 to 10. Always send the pool's whole size: autoscaling_enabled left out turns autoscaling off.
- `autoscaling_enabled` (boolean)
- `min_workers` (integer)
- `max_workers` (integer)

Example:

```sh
curl -X PATCH "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/node-pools/id_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "id_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: Bad request.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster or node pool not found.
- `409`: Cluster busy.
- `422`: Validation error.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
## Delete a node pool

```http
DELETE /public/v1/kubernetes-clusters/{cluster_id}/node-pools/{pool_id}
```

Removes a node pool: its workers leave the cluster (their pods move to other nodes that can run them) and are destroyed, then the pool goes. **Scope:** `write`. Destructive: an AI app's key also needs `delete`.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).
- `pool_id`: The node pool's id (`k8spool_…`).

Example:

```sh
curl -X DELETE "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/node-pools/id_06gk3x9tq7m2v8r4y1c5d0zp8w" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "id_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: Bad request.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster or node pool not found.
- `409`: Cluster busy.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
## List cluster add-ons

```http
GET /public/v1/kubernetes-clusters/{cluster_id}/addons
```

Lists the cluster's managed add-ons and what the cluster runs of each: `status` is `active`, `installing`, `removing`, `disabled` or `unavailable`. `metrics-server` gives `kubectl top` and the Horizontal Pod Autoscaler; `csi-driver` is persistent volumes on the node's own disk (StorageClass `local-path`; the data stays on that node); `ingress-controller` is ingress-nginx, the default IngressClass, served on 80 and 443 of the cluster's public address in a VPC zone. A cluster starts with metrics-server and csi-driver on.

Scope: `read`.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Example:

```sh
curl "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/addons" \
  -H "Authorization: Bearer $PANTECH_API_KEY"
```

Response `200`:

```json
{
  "data": [
    {
      "id": "id",
      "cluster_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
      "name": "web-1",
      "version": "version",
      "status": "active",
      "enabled": true,
      "configuration": "configuration",
      "created_at": "2026-10-03T09:30:00Z",
      "updated_at": "2026-10-03T09:30:00Z"
    }
  ],
  "next_cursor": "eyJhdCI6IjIwMjYtMTAtMDNUMDk6MzA6MDBaIiwiaWQiOiJ2bV8wNmdrIn0"
}
```

Errors:

- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster not found.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
## Toggle a cluster add-on

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/addons/{addon_name}/toggle
```

Turns a managed add-on on or off: `metrics-server`, `csi-driver` or `ingress-controller`. The platform applies it within moments and keeps it that way (a change made with kubectl is put back); the add-on's `status` shows when it has taken effect. Asking for the state it is already in changes nothing (empty `operation_id`).

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).
- `addon_name`: The add-on: `metrics-server`, `csi-driver` or `ingress-controller`. Any other name is `422 INVALID_CLUSTER_ADDON`.

Body:

- `enabled` (boolean, required): Whether the add-on should be enabled or disabled.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/addons/id_06gk3x9tq7m2v8r4y1c5d0zp8w/toggle" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "id_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: Bad request.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster or add-on not found.
- `409`: Cluster busy.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
## Configure API server endpoint access

```http
POST /public/v1/kubernetes-clusters/{cluster_id}/endpoint-access
```

Updates the cluster API endpoint access mode (`public`, `private`, or `dual`) and authorized CIDRs.

Scope: `write`. Send an `Idempotency-Key` header.

Path parameters:

- `cluster_id`: The cluster's id (`k8s_…`).

Body:

- `endpoint_access` (string, required): Endpoint access mode. One of `public`, `private`, `dual`.
- `api_allowed_cidrs` (array of string): IPv4 CIDRs allowed to connect to the cluster API.

Example:

```sh
curl -X POST "https://api.pantechdynamics.com/public/v1/kubernetes-clusters/k8s_06gk3x9tq7m2v8r4y1c5d0zp8w/endpoint-access" \
  -H "Authorization: Bearer $PANTECH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_access": "public"
  }'
```

Response `202`:

```json
{
  "operation_id": "op_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "resource_id": "k8s_06gk3x9tq7m2v8r4y1c5d0zp8w",
  "status": "submitting"
}
```

Errors:

- `400`: Bad request.
- `401`: `UNAUTHENTICATED`: the request has no API key, or the key is mistyped, revoked or expired, or its creator has left the organization. Send `Authorization: Bearer PAN_…` with a valid key; create one in the console under Organization › API keys.
- `403`: `INSUFFICIENT_SCOPE`: the API key lacks the scope this endpoint needs (`read` for GET, `write` for changes; an AI app's key also needs `delete` for a destructive call). Use a key with that scope, or reconnect the AI app with "Manage and delete" access.
`FORBIDDEN`: the member who created the key may not do this in the organization. Use a key created by an owner or admin.
`AI_APPS_DISABLED`: the organization has turned AI app access off, and this request was made with an AI app's key or sent by Pantech's MCP server (`Pantech-Client: mcp/…`). An owner can turn it on in the console under Organization › AI apps.
- `404`: Cluster not found.
- `409`: Cluster busy.
- `422`: Validation error.
- `429`: `RATE_LIMITED`: this API key, verified user or organization used its request budget for the current window, or this address exceeded an IP/failed-auth limit. Wait `Retry-After` seconds, then retry. For authenticated quota denials, `RateLimit-Scope` identifies the denying quota and `RateLimit-Reset` reports its remaining window in seconds.
- `503`: Service unavailable.
