Skip to content

API Reference

Workloads

List, filter, and inspect workloads (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs, CRDs) and their pods across every connected cluster.

Last updated


A workload in Kubeadapt is anything that owns pods — Deployment, StatefulSet, DaemonSet, Job, CronJob, or a custom-resource kind that fills the same role (Argo Rollout, NATS NatsCluster, GitHub EphemeralRunner, and similar). They share one Workload body. Four endpoints cover the surface: a cross-cluster list, a per-cluster scoped list, a single-workload lookup by Kubernetes metadata.uid, and a paginated pod sub-list for a workload.

All four endpoints accept ?cost_mode= and echo it on cost.cost_mode + meta.cost_mode. fully_loaded (the default) includes the workload's share of idle capacity and cluster overhead; workload_only is its direct compute cost, so the two amounts differ. See Cost Modes for the contract.

The ?kind= filter accepts only Deployment, StatefulSet, and DaemonSet. Job, CronJob, and CRD kinds (Rollout, NatsCluster, EphemeralRunner, and similar) cannot be filtered via this parameter; they still appear in unfiltered list responses with their actual metadata.workload_kind. To narrow to CRDs, omit the filter and check metadata.workload_kind in the response.

List workloads (cross-cluster)

GET /v1/workloads

Required scope: workloads:read

Flat cross-cluster list of every workload the API key can see — Deployments, StatefulSets, DaemonSets, and unfiltered CRDs. Default sort is cost.current_run_rate_hourly descending.

Query parameters

NameTypeDefaultAllowed valuesDescription
limitinteger1001..500Page size.
cursorstring(none)(any)Opaque pagination token from a previous response.
include_totalbooleanfalsetrue, falseInclude meta.pagination.total_count. Opt-in; computing the total count is a separate operation.
sortstringcostcost, idcost puts the highest run rate first. id sorts by workload UID, then cluster ID, an order that holds while you walk every page. See Sort policy.
cost_modestringfully_loadedfully_loaded, workload_onlyCost attribution mode. See Cost Modes.
cluster_idstring (csv)(none)UUIDsScope to one or more clusters.
namespacestring (csv)(none)(any)Filter by Kubernetes namespace.
kindstring (csv)(none)Deployment, StatefulSet, DaemonSetSee the caveat above. Other kinds appear unfiltered.
has_hpaboolean(none)true, falseFilter by HorizontalPodAutoscaler presence.
teamstring (csv)(none)(any)Filter by team name (resolved via team assignments).
departmentstring (csv)(none)(any)Filter by department name (two-hop via teams).
min_cost_hourlynumber(none)decimalDrop workloads cheaper than this hourly rate.

Example request

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads?kind=Deployment&has_hpa=true&limit=50&include_total=true"

Example response

json
1{
2  "data": [
3    {
4      "id": "b9f1a342-7c5e-4d8a-9e2b-1234567890ab",
5      "kind": "Workload",
6      "metadata": {
7        "name": "api-gateway",
8        "workload_kind": "Deployment",
9        "namespace": "payments",
10        "cluster": {
11          "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
12          "name": "prod-eu-west-1"
13        },
14        "labels": {
15          "app.kubernetes.io/name": "api-gateway",
16          "team": "payments",
17          "department": "engineering"
18        },
19        "service_account_name": "api-gateway",
20        "status": "Available",
21        "status_reason": "MinimumReplicasAvailable",
22        "is_suspended": false,
23        "is_paused": false,
24        "has_hpa": true,
25        "created_at_k8s": "2025-11-14T08:42:11Z",
26        "last_seen_at": "2026-05-21T10:29:48Z"
27      },
28      "capacity": {
29        "cpu": { "limit_cores": 6.0 },
30        "memory": { "limit_bytes": 12884901888 }
31      },
32      "utilization": {
33        "cpu": {
34          "requested_cores": 3.0,
35          "used_cores": 2.18,
36          "utilization_percent": 72.7
37        },
38        "memory": {
39          "requested_bytes": 6442450944,
40          "used_bytes": 4724464025,
41          "utilization_percent": 73.3
42        },
43        "replicas": {
44          "desired": 3,
45          "available": 3,
46          "unavailable": 0,
47          "updated": 3
48        },
49        "counts": {
50          "pods": 47,
51          "running_pods": 47,
52          "containers": 94
53        }
54      },
55      "cost": {
56        "current_run_rate_hourly": { "amount": "2.4000", "currency": "USD" },
57        "cost_mode": "fully_loaded",
58        "last_updated_at": "2026-05-21T10:29:48Z"
59      }
60    },
61    {
62      "id": "f3c2d4e5-8a1b-4c2d-9e3f-0a1b2c3d4e5f",
63      "kind": "Workload",
64      "metadata": {
65        "name": "postgres-primary",
66        "workload_kind": "StatefulSet",
67        "namespace": "data",
68        "cluster": {
69          "id": "d2b3c4d5-e6f7-8901-bcde-f23456789012",
70          "name": "prod-us-east-1"
71        },
72        "labels": {
73          "app.kubernetes.io/name": "postgres",
74          "role": "primary"
75        },
76        "service_account_name": "postgres",
77        "status": "Available",
78        "status_reason": "AllReplicasReady",
79        "is_suspended": false,
80        "is_paused": false,
81        "has_hpa": false,
82        "created_at_k8s": "2025-09-02T14:08:33Z",
83        "last_seen_at": "2026-05-21T10:29:50Z"
84      },
85      "capacity": {
86        "cpu": { "limit_cores": 2.0 },
87        "memory": { "limit_bytes": 8589934592 }
88      },
89      "utilization": {
90        "cpu": {
91          "requested_cores": 1.0,
92          "used_cores": 0.42,
93          "utilization_percent": 42.0
94        },
95        "memory": {
96          "requested_bytes": 4294967296,
97          "used_bytes": 3221225472,
98          "utilization_percent": 75.0
99        },
100        "replicas": {
101          "desired": 1,
102          "available": 1,
103          "unavailable": 0,
104          "updated": 1
105        },
106        "counts": {
107          "pods": 1,
108          "running_pods": 1,
109          "containers": 2
110        }
111      },
112      "cost": {
113        "current_run_rate_hourly": { "amount": "0.8400", "currency": "USD" },
114        "cost_mode": "fully_loaded",
115        "last_updated_at": "2026-05-21T10:29:50Z"
116      }
117    }
118  ],
119  "meta": {
120    "request_id": "0196f1c2-1edf-79b0-904e-ebe0aac49ff8",
121    "applied_at": "2026-05-21T10:30:00Z",
122    "pagination": {
123      "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6ImI5ZjFhMzQyLTdjNWUtNGQ4YS05ZTJiLTEyMzQ1Njc4OTBhYiIsImFmdGVyX3ZhbHVlIjoiMC44NDAwIiwicXVlcnlfaGFzaCI6ImEzYjRjNWQ2ZTdmOGc5YTBiMWMyZDNlNGY1ZzYiLCJpc3N1ZWRfYXQiOjE3MTYyODUwMDB9",
124      "has_more": true,
125      "limit": 50,
126      "total_count": 386
127    },
128    "cost_mode": "fully_loaded"
129  }
130}

Example error response

json
1{
2  "data": null,
3  "meta": {
4    "request_id": "0196f1c2-41c2-7b35-a4e9-27b9f07a20b2",
5    "applied_at": "2026-05-21T10:30:05Z"
6  },
7  "error": {
8    "code": "WORKLOAD_NOT_FOUND",
9    "message": "workload not found"
10  }
11}

Common errors

HTTPerror.codeWhen
400BAD_REQUESTlimit out of range, or min_cost_hourly is not a number.
400INVALID_CURSORThe cursor could not be decoded.
400INVALID_CLUSTER_IDA cluster_id filter value is not a valid UUID.
401UNAUTHORIZEDMissing or invalid Bearer token.
403FORBIDDENToken lacks the workloads:read scope.
403CLUSTER_ACCESS_DENIEDA cluster_id filter value is outside the key's allow-list.
410CURSOR_EXPIREDCursor older than 24h or filter set changed since it was issued.
422VALIDATION_ERRORkind or has_hpa has an unsupported value.
422INVALID_COST_MODEcost_mode outside fully_loaded/workload_only.
429RATE_LIMITED100 req/min/key by default. Honor Retry-After.
500INTERNAL_ERRORServer-side failure.

See Error Handling for the full taxonomy.

List workloads in cluster

GET /v1/clusters/{cluster_id}/workloads

Required scope: workloads:read

Same shape as the cross-cluster list, scoped to one cluster by path. The available filters and the default cost-descending sort are identical to GET /v1/workloads.

Path parameters

NameTypeDescription
cluster_idUUIDCluster identifier.

Query parameters

NameTypeDefaultAllowed valuesDescription
limitinteger1001..500Page size.
cursorstring(none)(any)Opaque pagination token.
include_totalbooleanfalsetrue, falseInclude meta.pagination.total_count.
sortstringcostcost, idcost puts the highest run rate first. id sorts by workload UID, then cluster ID, an order that holds while you walk every page. See Sort policy.
cost_modestringfully_loadedfully_loaded, workload_onlyCost attribution mode.
namespacestring (csv)(none)(any)Filter by namespace.
kindstring (csv)(none)Deployment, StatefulSet, DaemonSetSee the caveat above.
has_hpaboolean(none)true, falseHPA filter.
teamstring (csv)(none)(any)Filter by team name.
departmentstring (csv)(none)(any)Filter by department name.
min_cost_hourlynumber(none)decimalMinimum hourly cost.

Example request

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/clusters/c1a2b3c4-d5e6-7890-abcd-ef1234567890/workloads?namespace=payments,checkout&limit=100"

Example response

Returns an array of Workload 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.

json
1{
2  "data": [
3    {
4      "id": "e5f6a7b8-2c3d-4e5f-8a9b-0c1d2e3f4a5b",
5      "kind": "Workload",
6      "metadata": { "name": "payments-worker", "workload_kind": "Deployment", "namespace": "payments", "...": "..." },
7      "capacity": { "...": "..." },
8      "utilization": { "...": "..." },
9      "cost": { "current_run_rate_hourly": { "amount": "0.5200", "currency": "USD" }, "cost_mode": "fully_loaded", "last_updated_at": "2026-05-21T10:29:51Z" }
10    }
11  ],
12  "meta": {
13    "request_id": "0196f1c2-2dcb-77ca-bf3a-6f5e0fa071d9",
14    "applied_at": "2026-05-21T10:30:10Z",
15    "pagination": { "has_more": false, "limit": 100 },
16    "cost_mode": "fully_loaded"
17  }
18}

For the complete field list, see the canonical example above or the OpenAPI spec.

Common errors

HTTPerror.codeWhen
400INVALID_CLUSTER_IDcluster_id is not a valid UUID.
401UNAUTHORIZEDMissing or invalid Bearer token.
400BAD_REQUESTlimit out of range, or min_cost_hourly is not a number.
403CLUSTER_ACCESS_DENIEDToken does not include this cluster in its allowlist.
404CLUSTER_NOT_FOUNDNo cluster with this ID exists in your organization.
410CURSOR_EXPIREDCursor older than 24h or filter set changed.
422VALIDATION_ERRORBad filter value (kind=Job, has_hpa=yes, and so on).
422INVALID_COST_MODEcost_mode outside fully_loaded/workload_only.
429RATE_LIMITEDPer-key quota exceeded.
500INTERNAL_ERRORServer-side failure.

The cluster is checked before the list is read; see Routes under a cluster for the order of the checks.

Get workload by UID

GET /v1/workloads/{workload_uid}

Required scope: workloads:read

Fetch a single workload by its Kubernetes metadata.uid. The uid is the one Kubernetes stamps on the controller object: it survives spec edits (image bumps, env changes) but does not survive deletion and recreation. Names do not survive renames; uids do. Use this endpoint when you need a stable handle for cross-page links, alert payloads, or webhook targets.

A UID is unique within a cluster, not across clusters: a cluster registered twice holds two copies of every workload. 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

NameTypeDescription
workload_uidUUIDThe workload's Kubernetes metadata.uid. Not a Kubeadapt internal id.

Query parameters

NameTypeDefaultAllowed valuesDescription
cluster_idUUID(none)One UUIDLook the UID up in this cluster only. Required when the UID exists in more than one of your clusters.
cost_modestringfully_loadedfully_loaded, workload_onlyCost attribution mode.

Example request

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads/b9f1a342-7c5e-4d8a-9e2b-1234567890ab"

Example response

Returns a single Workload object. Same shape as one element of the cross-cluster list response above, wrapped directly under data (no array, no pagination in meta).

json
1{
2  "data": {
3    "id": "b9f1a342-7c5e-4d8a-9e2b-1234567890ab",
4    "kind": "Workload",
5    "metadata": { "name": "api-gateway", "workload_kind": "Deployment", "namespace": "payments", "...": "..." },
6    "capacity": { "...": "..." },
7    "utilization": { "...": "..." },
8    "cost": { "current_run_rate_hourly": { "amount": "2.4000", "currency": "USD" }, "cost_mode": "fully_loaded", "last_updated_at": "2026-05-21T10:29:48Z" }
9  },
10  "meta": {
11    "request_id": "0196f1c2-b4e5-79ea-9f1f-c7601e7ac7e8",
12    "applied_at": "2026-05-21T10:30:15Z",
13    "cost_mode": "fully_loaded"
14  }
15}

For the complete field list, see the canonical example in List workloads (cross-cluster) or the OpenAPI spec.

Common errors

HTTPerror.codeWhen
400BAD_REQUESTworkload_uid is not a valid UUID.
400INVALID_CLUSTER_IDcluster_id is not a valid UUID.
401UNAUTHORIZEDMissing or invalid Bearer token.
403FORBIDDENToken lacks the workloads:read scope.
403CLUSTER_ACCESS_DENIEDcluster_id names a cluster outside the key's allow-list.
404WORKLOAD_NOT_FOUNDNo workload with that uid in the clusters the key can read (or in cluster_id, when given).
422VALIDATION_ERRORThe uid exists in more than one of your clusters and cluster_id was not given. details[0].reason is ambiguous_workload_uid and details[0].allowed lists the cluster IDs to choose from.
422INVALID_COST_MODEcost_mode outside fully_loaded/workload_only.
429RATE_LIMITEDPer-key quota exceeded.
500INTERNAL_ERRORServer-side failure.

List pods for workload

GET /v1/workloads/{workload_uid}/pods

Required scope: workloads:read

The only pod-introspection surface in v1. There is no GET /v1/pods, GET /v1/clusters/{cluster_id}/pods, or GET /v1/clusters/{cluster_id}/namespaces/{namespace}/pods — pods are always returned as children of a single workload identified by its Kubernetes uid. A "list every pod in cluster X" query requires walking workloads first and calling this endpoint per workload.

Without cluster_id, the list covers every cluster the key can read that holds the workload UID. Pass cluster_id to keep one cluster's pods.

Path parameters

NameTypeDescription
workload_uidUUIDThe owning workload's Kubernetes metadata.uid.

Query parameters

NameTypeDefaultAllowed valuesDescription
limitinteger1001..500Page size.
cursorstring(none)(any)Opaque pagination token.
include_totalbooleanfalsetrue, falseInclude meta.pagination.total_count.
sortstringcostcost, idcost puts the highest run rate first. id sorts by pod UID, then cluster ID, an order that holds while you walk every page.
cluster_idUUID(none)One UUIDOnly pods in this cluster.
cost_modestringfully_loadedfully_loaded, workload_onlyCost attribution mode.
namespacestring (csv)(none)(any)Filter by namespace.
node_uidstring (csv)(none)UUIDsFilter by host node uid.
phasestring (csv)(none)Pending, Running, Succeeded, Failed, UnknownKubernetes pod phase.
qos_classstring (csv)(none)Guaranteed, Burstable, BestEffortQuality-of-service class.
has_hostpathboolean(none)true, falseFilter by hostPath volume presence.
has_emptydirboolean(none)true, falseFilter by emptyDir volume presence.
host_networkboolean(none)true, falseFilter by hostNetwork=true.

Example request

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/workloads/b9f1a342-7c5e-4d8a-9e2b-1234567890ab/pods?phase=Running&qos_class=Burstable"

Example response

json
1{
2  "data": [
3    {
4      "id": "p1q2r3s4-5t6u-7v8w-9x0y-1z2a3b4c5d6e",
5      "kind": "Pod",
6      "metadata": {
7        "name": "api-gateway-7fbd9d8c4-abc12",
8        "namespace": "payments",
9        "cluster": {
10          "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
11          "name": "prod-eu-west-1"
12        },
13        "workload": {
14          "uid": "b9f1a342-7c5e-4d8a-9e2b-1234567890ab",
15          "kind": "Deployment",
16          "name": "api-gateway"
17        },
18        "node": {
19          "id": "n1a2b3c4-d5e6-7890-abcd-ef0987654321",
20          "name": "ip-10-0-12-34.eu-west-1.compute.internal"
21        },
22        "phase": "Running",
23        "reason": "",
24        "qos_class": "Burstable",
25        "pod_ip": "10.0.12.34",
26        "host_ip": "10.0.0.124",
27        "host_network": false,
28        "priority_class": "production",
29        "has_hostpath": false,
30        "has_emptydir": true,
31        "labels": {
32          "app.kubernetes.io/name": "api-gateway",
33          "pod-template-hash": "7fbd9d8c4"
34        },
35        "created_at_k8s": "2026-05-19T06:18:42Z",
36        "last_seen_at": "2026-05-21T10:29:48Z"
37      },
38      "capacity": {
39        "cpu": { "limit_cores": 2.0 },
40        "memory": { "limit_bytes": 4294967296 }
41      },
42      "utilization": {
43        "cpu": {
44          "requested_cores": 1.0,
45          "used_cores": 0.73,
46          "utilization_percent": 73.0
47        },
48        "memory": {
49          "requested_bytes": 2147483648,
50          "used_bytes": 1610612736,
51          "utilization_percent": 75.0
52        },
53        "counts": {
54          "containers": 2,
55          "running_containers": 2,
56          "ready_containers": 2
57        },
58        "restarts_total": 0,
59        "oom_kills_total": 0
60      },
61      "cost": {
62        "current_run_rate_hourly": { "amount": "0.0840", "currency": "USD" },
63        "cost_mode": "fully_loaded",
64        "last_updated_at": "2026-05-21T10:29:48Z"
65      }
66    }
67  ],
68  "meta": {
69    "request_id": "0196f1c2-0ef3-7daf-8604-7b996b1386b0",
70    "applied_at": "2026-05-21T10:30:20Z",
71    "pagination": {
72      "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6InAxcTJyM3M0LTV0NnUtN3Y4dy05eDB5LTF6MmEzYjRjNWQ2ZSIsImFmdGVyX3ZhbHVlIjoiMC4wODQwIiwicXVlcnlfaGFzaCI6ImQ0ZTVmNmc3aDhpOWowYTFiMmMzZDRlNWY2ZzciLCJpc3N1ZWRfYXQiOjE3MTYyODUwMjB9",
73      "has_more": true,
74      "limit": 100
75    },
76    "cost_mode": "fully_loaded"
77  }
78}

Common errors

HTTPerror.codeWhen
400BAD_REQUESTworkload_uid is not a valid UUID, or another path/query value is malformed.
400INVALID_CLUSTER_IDcluster_id is not a valid UUID.
401UNAUTHORIZEDMissing or invalid Bearer token.
403FORBIDDENToken lacks the workloads:read scope.
403CLUSTER_ACCESS_DENIEDcluster_id names a cluster outside the key's allow-list.
404WORKLOAD_NOT_FOUNDNo workload with that uid in the clusters the key can read (or in cluster_id, when given).
410CURSOR_EXPIREDCursor older than 24h or filter set changed.
422VALIDATION_ERRORphase, qos_class, or a boolean filter has an unsupported value. The error detail names the offending field.
429RATE_LIMITEDPer-key quota exceeded.
500INTERNAL_ERRORServer-side failure.

See also

  • Clusters: the top-level resource that owns every workload returned here.
  • Namespaces: group workloads by Kubernetes namespace and read namespace-level cost.
  • Nodes: resolve the node a pod runs on (metadata.node.id) to its instance type and spot/on-demand status.
  • Recommendations: workload_rightsizing findings for the workloads listed above.
  • Cost Modes: fully_loaded vs workload_only and which endpoints accept each mode.
  • Cost Trends: per-workload cost time series by k8s metadata.uid.