API Reference
Nodes
List, filter, and inspect Kubernetes nodes across every connected cluster, including instance type, spot status, capacity, and physical hourly cost.
Last updated
Three endpoints cover the node surface: a cross-cluster flat list, a per-cluster scoped list, and a single-node lookup by Kubernetes metadata.uid. Filters include is_spot, instance_type, node_group, architecture, and capacity_type.
List nodes (cross-cluster)
GET /v1/nodes
Required scope: nodes:read
A flat, cost-sorted inventory of every node the API key can see, across every cluster in the allow-list.
Default sort is cost.current_run_rate_hourly descending.
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
limit | integer | 100 | 1..500 | Page size. |
cursor | string | (none) | (any) | Opaque pagination token from a previous response. |
include_total | boolean | false | true, false | Include meta.pagination.total_count. |
sort | string | cost | cost, id | cost puts the highest run rate first. id sorts by node UID, then cluster ID, an order that holds while you walk every page. See Sort policy. |
cluster_id | string (csv) | (none) | UUIDs | Scope to one or more clusters. |
node_group | string (csv) | (none) | (any) | Filter by node-group label value. |
instance_type | string (csv) | (none) | (any) | Filter by cloud instance type. Singular here; the response field on a node-group is the plural array metadata.instance_types. |
zone | string (csv) | (none) | (any) | Filter by availability zone. |
is_spot | boolean | (none) | true, false | Spot/preemptible vs on-demand. |
is_ready | boolean | (none) | true, false | Kubernetes Ready condition. |
architecture | string (csv) | (none) | amd64, arm64 | CPU architecture. |
capacity_type | string (csv) | (none) | on-demand, spot, reserved, karpenter | Pricing capacity bucket as the cloud provider reports it. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/nodes?is_spot=false&architecture=amd64&limit=50&include_total=true"Example response
1{
2 "data": [
3 {
4 "id": "n1a2b3c4-d5e6-7890-abcd-ef0987654321",
5 "kind": "Node",
6 "metadata": {
7 "name": "ip-10-0-12-34.eu-west-1.compute.internal",
8 "cluster": {
9 "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
10 "name": "prod-eu-west-1"
11 },
12 "node_role": "worker",
13 "instance_type": "m5.2xlarge",
14 "node_group": "prod-mixed",
15 "availability_zone": "eu-west-1a",
16 "region": "eu-west-1",
17 "is_spot": false,
18 "capacity_type": "on-demand",
19 "architecture": "amd64",
20 "operating_system": "linux",
21 "kubelet_version": "v1.29.4-eks-7c54e62",
22 "provider_id": "aws:///eu-west-1a/i-0a1b2c3d4e5f67890",
23 "is_ready": true,
24 "is_schedulable": true,
25 "labels": {
26 "kubernetes.io/arch": "amd64",
27 "node.kubernetes.io/instance-type": "m5.2xlarge",
28 "topology.kubernetes.io/zone": "eu-west-1a"
29 },
30 "created_at_k8s": "2026-04-22T15:08:11Z",
31 "last_seen_at": "2026-05-21T10:29:48Z"
32 },
33 "capacity": {
34 "cpu": { "total_cores": 8.0, "allocatable_cores": 7.91 },
35 "memory": { "total_bytes": 34359738368, "allocatable_bytes": 32212254720 },
36 "gpu": { "total": 0, "allocatable": 0, "model": "" },
37 "ephemeral_storage": { "total_bytes": 107374182400 },
38 "pods": { "allocatable": 58 }
39 },
40 "utilization": {
41 "cpu": { "used_cores": 6.32, "utilization_percent": 79.9 },
42 "memory": { "used_bytes": 25769803776, "utilization_percent": 80.0 },
43 "gpu": {
44 "utilization_percent": 0,
45 "memory_used_bytes": 0,
46 "memory_total_bytes": 0
47 },
48 "counts": { "pods": 24, "running_pods": 22 }
49 },
50 "cost": {
51 "current_run_rate_hourly": { "amount": "0.4960", "currency": "USD" },
52 "on_demand_equivalent_hourly": { "amount": "0.4960", "currency": "USD" },
53 "pricing_source": "aws-pricing-api",
54 "last_updated_at": "2026-05-21T10:29:48Z"
55 }
56 },
57 {
58 "id": "n2b3c4d5-e6f7-8901-bcde-f12345678901",
59 "kind": "Node",
60 "metadata": {
61 "name": "ip-10-0-15-78.eu-west-1.compute.internal",
62 "cluster": {
63 "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
64 "name": "prod-eu-west-1"
65 },
66 "node_role": "worker",
67 "instance_type": "m5.2xlarge",
68 "node_group": "prod-mixed",
69 "availability_zone": "eu-west-1b",
70 "region": "eu-west-1",
71 "is_spot": true,
72 "capacity_type": "spot",
73 "architecture": "amd64",
74 "operating_system": "linux",
75 "kubelet_version": "v1.29.4-eks-7c54e62",
76 "provider_id": "aws:///eu-west-1b/i-1b2c3d4e5f6a78901",
77 "is_ready": true,
78 "is_schedulable": true,
79 "labels": {
80 "kubernetes.io/arch": "amd64",
81 "node.kubernetes.io/instance-type": "m5.2xlarge",
82 "node.kubernetes.io/lifecycle": "spot",
83 "topology.kubernetes.io/zone": "eu-west-1b"
84 },
85 "created_at_k8s": "2026-04-30T07:42:18Z",
86 "last_seen_at": "2026-05-21T10:29:49Z"
87 },
88 "capacity": {
89 "cpu": { "total_cores": 8.0, "allocatable_cores": 7.91 },
90 "memory": { "total_bytes": 34359738368, "allocatable_bytes": 32212254720 },
91 "gpu": { "total": 0, "allocatable": 0, "model": "" },
92 "ephemeral_storage": { "total_bytes": 107374182400 },
93 "pods": { "allocatable": 58 }
94 },
95 "utilization": {
96 "cpu": { "used_cores": 5.06, "utilization_percent": 64.0 },
97 "memory": { "used_bytes": 22441820160, "utilization_percent": 69.7 },
98 "gpu": {
99 "utilization_percent": 0,
100 "memory_used_bytes": 0,
101 "memory_total_bytes": 0
102 },
103 "counts": { "pods": 19, "running_pods": 19 }
104 },
105 "cost": {
106 "current_run_rate_hourly": { "amount": "0.1492", "currency": "USD" },
107 "on_demand_equivalent_hourly": { "amount": "0.4960", "currency": "USD" },
108 "pricing_source": "aws-pricing-api",
109 "last_updated_at": "2026-05-21T10:29:49Z"
110 }
111 }
112 ],
113 "meta": {
114 "request_id": "0196f1c2-acfc-7c1a-ac5d-915eb3d29dbc",
115 "applied_at": "2026-05-21T10:31:00Z",
116 "pagination": {
117 "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6Im4yYjNjNGQ1LWU2ZjctODkwMS1iY2RlLWYxMjM0NTY3ODkwMSIsImFmdGVyX3ZhbHVlIjoiMC4xNDkyIiwicXVlcnlfaGFzaCI6ImI0YzVkNmU3ZjhnOWgwaTFqMmszbDRtNW42cDciLCJpc3N1ZWRfYXQiOjE3MTYyODUwNjB9",
118 "has_more": true,
119 "limit": 50,
120 "total_count": 32
121 }
122 }
123}Example error response
1{
2 "data": null,
3 "meta": {
4 "request_id": "0196f1c2-cc7c-7c4f-bad7-b8a7f1218867",
5 "applied_at": "2026-05-21T10:31:05Z"
6 },
7 "error": {
8 "code": "INVALID_COST_MODE",
9 "message": "cost_mode is not supported on this endpoint",
10 "details": [
11 { "field": "cost_mode", "reason": "not_supported_on_this_endpoint" }
12 ]
13 }
14}Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | Bad cursor, limit out of range, malformed UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks the nodes:read scope. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or filter set changed. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. This endpoint doesn't accept it. |
| 422 | VALIDATION_ERROR | is_spot, is_ready, architecture, capacity_type, or another filter has an unsupported value. The error detail names the offending field. |
| 429 | RATE_LIMITED | 100 req/min/key by default. Honor Retry-After. |
See Error Handling.
List nodes in cluster
GET /v1/clusters/{cluster_id}/nodes
Required scope: nodes:read
Same shape as the cross-cluster list, scoped to one cluster by path. All filters except cluster_id apply.
Path parameters
| Name | Type | Description |
|---|---|---|
cluster_id | UUID | Cluster identifier. |
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
limit | integer | 100 | 1..500 | Page size. |
cursor | string | (none) | (any) | Opaque pagination token. |
include_total | boolean | false | true, false | Include meta.pagination.total_count. |
sort | string | cost | cost, id | cost puts the highest run rate first. id sorts by node UID, then cluster ID, an order that holds while you walk every page. See Sort policy. |
node_group | string (csv) | (none) | (any) | Filter by node-group label value. |
instance_type | string (csv) | (none) | (any) | Filter by cloud instance type. |
zone | string (csv) | (none) | (any) | Filter by availability zone. |
is_spot | boolean | (none) | true, false | Spot vs on-demand. |
is_ready | boolean | (none) | true, false | Kubernetes Ready condition. |
architecture | string (csv) | (none) | amd64, arm64 | CPU architecture. |
capacity_type | string (csv) | (none) | on-demand, spot, reserved, karpenter | Pricing capacity bucket. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/clusters/c1a2b3c4-d5e6-7890-abcd-ef1234567890/nodes?is_spot=true&limit=100"Example response
Returns an array of Node 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, and meta.pagination follows the same cursor rules.
1{
2 "data": [
3 {
4 "id": "n3c4d5e6-f7a8-9012-cdef-123456789012",
5 "kind": "Node",
6 "metadata": { "name": "ip-10-0-22-91.eu-west-1.compute.internal", "instance_type": "m5.4xlarge", "is_spot": true, "capacity_type": "spot", "availability_zone": "eu-west-1c", "...": "..." },
7 "capacity": { "...": "..." },
8 "utilization": { "...": "..." },
9 "cost": { "current_run_rate_hourly": { "amount": "0.2984", "currency": "USD" }, "on_demand_equivalent_hourly": { "amount": "0.9920", "currency": "USD" }, "pricing_source": "aws-pricing-api", "last_updated_at": "2026-05-21T10:29:51Z" }
10 }
11 ],
12 "meta": {
13 "request_id": "0196f1c2-b5f2-7b3d-aba3-56257c8bc99e",
14 "applied_at": "2026-05-21T10:31:15Z",
15 "pagination": { "has_more": false, "limit": 100 }
16 }
17}For the complete field list, see the canonical example in List nodes (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 | No cluster with this ID exists in your organization. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or filter set changed. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. |
| 422 | VALIDATION_ERROR | Bad filter value. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
The cluster is checked before the list is read; see Routes under a cluster for the order of the checks.
Get node by UID
GET /v1/nodes/{node_uid}
Required scope: nodes:read
Fetch a single node by its Kubernetes metadata.uid. The uid is stable across kubelet restarts and survives the node's full lifecycle; names like ip-10-0-12-34.eu-west-1.compute.internal change when the underlying instance is replaced. Pod responses surface the uid as metadata.node.id; recommendations surface it as resource_uid.
A UID is unique within a cluster, not across clusters: a cluster registered twice holds two copies of every node. Without cluster_id, the lookup searches every cluster the key can read. If more than one of them holds the UID, the request returns 422 VALIDATION_ERROR and lists the candidate clusters; retry with one of them as cluster_id.
Path parameters
| Name | Type | Description |
|---|---|---|
node_uid | UUID | The node's Kubernetes metadata.uid. |
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
cluster_id | UUID | (none) | One UUID | Look the UID up in this cluster only. Required when the UID exists in more than one of your clusters. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/nodes/n1a2b3c4-d5e6-7890-abcd-ef0987654321"Example response
Returns a single Node object. Same shape as one element of the List nodes (cross-cluster) response above, wrapped directly under data (no array, no pagination in meta).
1{
2 "data": {
3 "id": "n1a2b3c4-d5e6-7890-abcd-ef0987654321",
4 "kind": "Node",
5 "metadata": { "name": "ip-10-0-12-34.eu-west-1.compute.internal", "instance_type": "m5.2xlarge", "is_spot": false, "capacity_type": "on-demand", "availability_zone": "eu-west-1a", "...": "..." },
6 "capacity": { "...": "..." },
7 "utilization": { "...": "..." },
8 "cost": { "current_run_rate_hourly": { "amount": "0.4960", "currency": "USD" }, "on_demand_equivalent_hourly": { "amount": "0.4960", "currency": "USD" }, "pricing_source": "aws-pricing-api", "last_updated_at": "2026-05-21T10:29:48Z" }
9 },
10 "meta": {
11 "request_id": "0196f1c2-f327-79b2-b9d9-31571ca91c72",
12 "applied_at": "2026-05-21T10:31:20Z"
13 }
14}For the complete field list, see the canonical example in List nodes (cross-cluster) or the OpenAPI spec.
Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | node_uid is not a valid UUID. |
| 400 | INVALID_CLUSTER_ID | cluster_id is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks the nodes:read scope. |
| 403 | CLUSTER_ACCESS_DENIED | cluster_id names a cluster outside the key's allow-list. |
| 404 | NODE_NOT_FOUND | No node with that uid in the clusters the key can read (or in cluster_id, when given). |
| 422 | VALIDATION_ERROR | The uid exists in more than one of your clusters and cluster_id was not given. details[0].reason is ambiguous_node_uid and details[0].allowed lists the cluster IDs to choose from. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
See also
- Node Groups: group-level aggregates over the nodes returned here (spot percentage, instance-type mix, group savings).
- Clusters: the parent cluster identified by
metadata.cluster.idon every node. - Workloads: list workloads, then call
/podsto see which nodes their pods land on. - Recommendations:
workload_rightsizingfindings whose target pods land on these nodes. - Cost Modes: why nodes reject
?cost_mode=and which endpoints accept it.