API Reference
Node Groups
List and inspect node-group aggregates (Karpenter NodePools, EKS Managed Node Groups, GKE node pools) with instance-type mix, spot share, and group-level savings.
A node group is the autoscaling unit one tier above a node: a Karpenter NodePool, an EKS Managed Node Group, a GKE node pool, an AKS agent pool. Node-group responses expose the spot-vs-on-demand mix, the set of backing instance types, and the dollar value spot is saving against the on-demand equivalent. Three endpoints cover the surface: a cross-cluster list, a per-cluster scoped list, and a single-group detail endpoint.
Field-naming notes:
metadata.instance_typeson a node group is a plural array — a single autoscaling group can mixm5.2xlargeandm5.4xlarge.- The
?instance_type=query filter on the Nodes endpoints is a singular CSV that filters individual nodes, not node groups. - The
NodeGroupschema reserves a top-levelnodesarray for the member-node list, but no endpoint populates it. Treatnodesas absent from every response.
List node-groups (cross-cluster)
GET /v1/node-groups
Required scope: nodes:read (node-groups are covered by the nodes scope; there is no separate node-groups:read).
A flat list of every node group the API key can see, across every cluster. No pagination.
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
cluster_id | string (csv) | (none) | UUIDs | Scope to one or more clusters. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/node-groups?cluster_id=c1a2b3c4-d5e6-7890-abcd-ef1234567890,d2b3c4d5-e6f7-8901-bcde-f23456789012"Example response
1{
2 "data": [
3 {
4 "id": "ng-c1a2b3c4-d5e6-7890-abcd-ef1234567890-prod-mixed",
5 "kind": "NodeGroup",
6 "metadata": {
7 "name": "prod-mixed",
8 "cluster": {
9 "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
10 "name": "prod-eu-west-1"
11 },
12 "instance_types": ["m5.2xlarge", "m5.4xlarge"],
13 "zones": ["eu-west-1a", "eu-west-1b", "eu-west-1c"],
14 "spot_count": 12,
15 "ondemand_count": 8,
16 "spot_percentage": 60.0,
17 "oldest_node_created_at_k8s": "2026-02-14T09:18:22Z",
18 "status": "healthy"
19 },
20 "capacity": {
21 "cpu": { "total_cores": 192.0, "allocatable_cores": 189.84 },
22 "memory": { "total_bytes": 824633720832, "allocatable_bytes": 773094113280 }
23 },
24 "utilization": {
25 "cpu": { "used_cores": 121.92, "utilization_percent": 63.5 },
26 "memory": { "used_bytes": 532575944704, "utilization_percent": 64.6 },
27 "counts": { "nodes": 20, "ready_nodes": 20, "pods": 380 }
28 },
29 "cost": {
30 "current_run_rate_hourly": { "amount": "4.8400", "currency": "USD" },
31 "spot_savings_vs_ondemand_hourly": { "amount": "2.4100", "currency": "USD" },
32 "last_updated_at": "2026-05-21T10:29:54Z"
33 }
34 },
35 {
36 "id": "ng-d2b3c4d5-e6f7-8901-bcde-f23456789012-gpu-training",
37 "kind": "NodeGroup",
38 "metadata": {
39 "name": "gpu-training",
40 "cluster": {
41 "id": "d2b3c4d5-e6f7-8901-bcde-f23456789012",
42 "name": "prod-us-central-1"
43 },
44 "instance_types": ["g2-standard-8"],
45 "zones": ["us-central1-a"],
46 "spot_count": 0,
47 "ondemand_count": 4,
48 "spot_percentage": 0.0,
49 "oldest_node_created_at_k8s": "2026-01-08T17:42:09Z",
50 "status": "degraded"
51 },
52 "capacity": {
53 "cpu": { "total_cores": 32.0, "allocatable_cores": 31.6 },
54 "memory": { "total_bytes": 137438953472, "allocatable_bytes": 128849018880 }
55 },
56 "utilization": {
57 "cpu": { "used_cores": 22.4, "utilization_percent": 70.0 },
58 "memory": { "used_bytes": 96636764160, "utilization_percent": 70.3 },
59 "counts": { "nodes": 4, "ready_nodes": 3, "pods": 28 }
60 },
61 "cost": {
62 "current_run_rate_hourly": { "amount": "7.9200", "currency": "USD" },
63 "spot_savings_vs_ondemand_hourly": { "amount": "0.0000", "currency": "USD" },
64 "last_updated_at": "2026-05-21T10:29:56Z"
65 }
66 }
67 ],
68 "meta": {
69 "request_id": "req_01J5K4B5U3C9D4GW8H9I1K3L5F",
70 "applied_at": "2026-05-21T10:32:00Z"
71 }
72}Example error response
1{
2 "data": null,
3 "meta": {
4 "request_id": "req_01J5K4B6V4D0E5HX9I0J2L4M6G",
5 "applied_at": "2026-05-21T10:32:05Z"
6 },
7 "error": {
8 "code": "NODE_GROUP_NOT_FOUND",
9 "message": "no node-group with that name in this cluster",
10 "details": [
11 { "field": "name", "reason": "not_found" }
12 ]
13 }
14}Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | Malformed UUID in cluster_id. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks the nodes:read scope. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. This endpoint doesn't accept it. |
| 422 | VALIDATION_ERROR | cluster_id value rejected for another reason. |
| 429 | RATE_LIMITED | 100 req/min/key by default. Honor Retry-After. |
See Error Handling.
List node-groups in cluster
GET /v1/clusters/{cluster_id}/node-groups
Required scope: nodes:read
Same shape, scoped to one cluster by path. No filters, no pagination.
Path parameters
| Name | Type | Description |
|---|---|---|
cluster_id | UUID | Cluster identifier. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/clusters/c1a2b3c4-d5e6-7890-abcd-ef1234567890/node-groups"Example response
Returns an array of NodeGroup objects scoped to the path cluster. Same shape as the cross-cluster list above — metadata.cluster.id will match the path cluster_id on every row.
1{
2 "data": [
3 {
4 "id": "ng-c1a2b3c4-d5e6-7890-abcd-ef1234567890-prod-mixed",
5 "kind": "NodeGroup",
6 "metadata": { "name": "prod-mixed", "cluster": { "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890", "name": "prod-eu-west-1" }, "instance_types": ["m5.2xlarge", "m5.4xlarge"], "spot_percentage": 60.0, "status": "healthy", "...": "..." },
7 "capacity": { "...": "..." },
8 "utilization": { "...": "..." },
9 "cost": { "current_run_rate_hourly": { "amount": "4.8400", "currency": "USD" }, "spot_savings_vs_ondemand_hourly": { "amount": "2.4100", "currency": "USD" }, "last_updated_at": "2026-05-21T10:29:54Z" }
10 }
11 ],
12 "meta": {
13 "request_id": "req_01J5K4B7W5E1F6IY0J1K3M5N7H",
14 "applied_at": "2026-05-21T10:32:10Z"
15 }
16}For the complete field list, see the canonical example in List node-groups (cross-cluster) or the OpenAPI spec.
Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | INVALID_CLUSTER_ID | cluster_id is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | CLUSTER_ACCESS_DENIED | Token does not include this cluster in its allowlist. |
| 404 | CLUSTER_NOT_FOUND | Cluster UUID well-formed but not present in the tenant. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
Get node-group by name
GET /v1/clusters/{cluster_id}/node-groups/{name}
Required scope: nodes:read
Fetch a single node group by its label value within a cluster. The name is the value of the node_group label on the underlying nodes (whatever Karpenter, EKS, GKE, or AKS wrote there at provision time). The body is identical in shape to a list element; there is no extra detail and no member-node array.
Path parameters
| Name | Type | Description |
|---|---|---|
cluster_id | UUID | Cluster identifier. |
name | string | The node-group label value (for example, prod-mixed, gpu-training). |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/clusters/c1a2b3c4-d5e6-7890-abcd-ef1234567890/node-groups/prod-mixed"Example response
Returns a single NodeGroup object. Same shape as one element of the List node-groups (cross-cluster) response above, wrapped directly under data (no array, no pagination in meta). The nodes array reserved on the schema is absent from this response. To get the member nodes, call GET /v1/clusters/{cluster_id}/nodes?node_group=prod-mixed instead.
1{
2 "data": {
3 "id": "ng-c1a2b3c4-d5e6-7890-abcd-ef1234567890-prod-mixed",
4 "kind": "NodeGroup",
5 "metadata": { "name": "prod-mixed", "cluster": { "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890", "name": "prod-eu-west-1" }, "instance_types": ["m5.2xlarge", "m5.4xlarge"], "spot_percentage": 60.0, "status": "healthy", "...": "..." },
6 "capacity": { "...": "..." },
7 "utilization": { "...": "..." },
8 "cost": { "current_run_rate_hourly": { "amount": "4.8400", "currency": "USD" }, "spot_savings_vs_ondemand_hourly": { "amount": "2.4100", "currency": "USD" }, "last_updated_at": "2026-05-21T10:29:54Z" }
9 },
10 "meta": {
11 "request_id": "req_01J5K4B8X6F2G7JZ1K2L4N6P8I",
12 "applied_at": "2026-05-21T10:32:20Z"
13 }
14}For the complete field list, see the canonical example in List node-groups (cross-cluster) or the OpenAPI spec.
Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | INVALID_CLUSTER_ID | cluster_id is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | CLUSTER_ACCESS_DENIED | Token does not include this cluster in its allowlist. |
| 404 | CLUSTER_NOT_FOUND | Cluster UUID well-formed but not present in the tenant. |
| 404 | NODE_GROUP_NOT_FOUND | Cluster exists but has no node-group with that name. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
See also
- Nodes: the per-node bodies that aggregate into the groups returned here. Use
?node_group=to list members. - Clusters: the parent cluster identified by
metadata.cluster.idon every node-group. - Recommendations:
workload_rightsizingfindings whose target pods schedule onto these groups. - Cost Modes: why node groups reject
?cost_mode=and which endpoints accept it. - Cost Trends: per-node-group cost time series (URL-encode the group name).