API Reference
Workloads
List, filter, and inspect workloads (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs, CRDs) and their pods across every connected cluster.
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. Both modes currently return the same amount for workload responses. 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
| 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. Opt-in; computing the total count is a separate operation. |
cost_mode | string | fully_loaded | fully_loaded, workload_only | Cost attribution mode. See Cost Modes. |
cluster_id | string (csv) | (none) | UUIDs | Scope to one or more clusters. |
namespace | string (csv) | (none) | (any) | Filter by Kubernetes namespace. |
kind | string (csv) | (none) | Deployment, StatefulSet, DaemonSet | See the caveat above. Other kinds appear unfiltered. |
has_hpa | boolean | (none) | true, false | Filter by HorizontalPodAutoscaler presence. |
team | string (csv) | (none) | (any) | Filter by team name (resolved via team assignments). |
department | string (csv) | (none) | (any) | Filter by department name (two-hop via teams). |
min_cost_hourly | number | (none) | decimal | Drop workloads cheaper than this hourly rate. |
Example request
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
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": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9F",
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
1{
2 "data": null,
3 "meta": {
4 "request_id": "req_01J5K3V1RQ8Z5XS9B3C4D6E8F0",
5 "applied_at": "2026-05-21T10:30:05Z"
6 },
7 "error": {
8 "code": "WORKLOAD_NOT_FOUND",
9 "message": "no workload with that uid in this tenant",
10 "details": [
11 { "field": "workload_uid", "reason": "not_found" }
12 ]
13 }
14}Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | Malformed UUID, bad cursor, limit out of range. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks the workloads:read scope. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or filter set changed since it was issued. |
| 422 | VALIDATION_ERROR | kind, has_hpa, min_cost_hourly, or another filter has an unsupported value. |
| 429 | RATE_LIMITED | 100 req/min/key by default. Honor Retry-After. |
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
| 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. |
cost_mode | string | fully_loaded | fully_loaded, workload_only | Cost attribution mode. |
namespace | string (csv) | (none) | (any) | Filter by namespace. |
kind | string (csv) | (none) | Deployment, StatefulSet, DaemonSet | See the caveat above. |
has_hpa | boolean | (none) | true, false | HPA filter. |
team | string (csv) | (none) | (any) | Filter by team name. |
department | string (csv) | (none) | (any) | Filter by department name. |
min_cost_hourly | number | (none) | decimal | Minimum hourly cost. |
Example request
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.
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": "req_01J5K3V2S8A6Y0DT4C5E7F9G1B",
14 "applied_at": "2026-05-21T10:30:10Z",
15 "pagination": { "next_cursor": "", "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
| 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. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or filter set changed. |
| 422 | VALIDATION_ERROR | Bad filter value (kind=Job, has_hpa=yes, and so on). |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
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.
Path parameters
| Name | Type | Description |
|---|---|---|
workload_uid | UUID | The workload's Kubernetes metadata.uid. Not a Kubeadapt internal id. |
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
cost_mode | string | fully_loaded | fully_loaded, workload_only | Cost attribution mode. |
Example request
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).
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": "req_01J5K3V3T9B7Z1EU5D6F8G0H2C",
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
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | workload_uid is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks the workloads:read scope. |
| 404 | WORKLOAD_NOT_FOUND | UUID well-formed but no workload with that uid in this tenant. |
| 422 | VALIDATION_ERROR | cost_mode outside fully_loaded/workload_only. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
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.
Path parameters
| Name | Type | Description |
|---|---|---|
workload_uid | UUID | The owning workload's Kubernetes metadata.uid. |
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. |
cost_mode | string | fully_loaded | fully_loaded, workload_only | Cost attribution mode. |
namespace | string (csv) | (none) | (any) | Filter by namespace. |
node_uid | string (csv) | (none) | UUIDs | Filter by host node uid. |
phase | string (csv) | (none) | Pending, Running, Succeeded, Failed, Unknown | Kubernetes pod phase. |
qos_class | string (csv) | (none) | Guaranteed, Burstable, BestEffort | Quality-of-service class. |
has_hostpath | boolean | (none) | true, false | Filter by hostPath volume presence. |
has_emptydir | boolean | (none) | true, false | Filter by emptyDir volume presence. |
host_network | boolean | (none) | true, false | Filter by hostNetwork=true. |
Example request
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
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": "req_01J5K3V4U0C8A2FV6E7G9H1J3D",
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
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | workload_uid is not a valid UUID, or another path/query value is malformed. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks the workloads:read scope. |
| 404 | WORKLOAD_NOT_FOUND | No workload with that uid in this tenant. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or filter set changed. |
| 422 | VALIDATION_ERROR | phase, qos_class, or a boolean filter has an unsupported value. The error detail names the offending field. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
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_rightsizingfindings for the workloads listed above. - Cost Modes:
fully_loadedvsworkload_onlyand which endpoints accept each mode. - Cost Trends: per-workload cost time series by k8s
metadata.uid.