API Reference
Recommendations
List and inspect workload rightsizing recommendations.
Two endpoints cover the recommendations surface: a paginated list with filters and a single-recommendation lookup by UUID. Each recommendation pairs a current configuration with a recommended one, an estimated hourly saving, a risk level, and a priority bucket. Both endpoints reject ?cost_mode= — recommendation savings are mode-agnostic.
Schema shape
Recommendations do not use the metadata / capacity / utilization / cost block layout used by Clusters, Namespaces, Nodes, and Workloads. The top-level structure is:
metadata # identity, status, lifecycle timestamps, priority bucket
current # config + hourly_cost of what's running today
recommended # config the API suggests
applied # config that was applied (empty until status=applied)
savings # estimated hourly savingsThe savings block carries a single field — estimated_hourly (a Money object). To compute a monthly projection, multiply estimated_hourly by the number of hours in a month. The detail endpoint adds a metrics_snapshot block — an open-ended object holding usage percentiles, sample count, and the analysis window. The public API exposes only workload_rightsizing recommendations, so every config block carries CPU and memory request/limit pairs. Treat each config block as opaque JSON; additional fields may appear without an API version bump.
The list sorts by savings.estimated_hourly descending. priority is read-only and reflects the current estimated savings.
List recommendations
GET /v1/recommendations
Required scope: recommendations:read
Backlog of cost optimizations across the API key's allowed clusters. Paginated with the standard cursor + limit; see Pagination & Filtering for cursor semantics.
Query parameters
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
limit | integer | 100 | 1..500 | Page size. |
cursor | string | Opaque pagination token from a previous response. | ||
include_total | boolean | false | true, false | Include meta.pagination.total_count. Opt-in. |
cluster_id | string (csv) | UUID list | Restrict to specific clusters. Subject to the API key's cluster ACL. | |
namespace | string (csv) | Restrict to specific namespaces. | ||
recommendation_type | string (csv) | workload_rightsizing | Public API surfaces only workload_rightsizing; passing any other value yields 422 VALIDATION_ERROR. | |
status | string (csv) | canonical filter | pending, applied, dismissed, archived | When omitted, applies status IN (pending, dismissed) AND recommendation_type = 'workload_rightsizing'. Pass an explicit value to broaden. |
risk_level | string (csv) | low, medium, high | Engine-assigned risk of applying the recommendation. | |
priority | string (csv) | low, medium, high | Bucket assigned from estimated monthly savings: high (≥ $50/mo), medium (≥ $10/mo), low (otherwise). Read-only. | |
resource_type | string (csv) | Deployment, StatefulSet, DaemonSet, Pod, Node | Restrict to specific resource kinds. | |
workload_uid | string (csv) | k8s UIDs | Restrict to specific workload UIDs. | |
min_savings_hourly | number | decimal | Minimum hourly savings amount. Useful for cutting the long tail. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/recommendations?recommendation_type=workload_rightsizing&priority=high&min_savings_hourly=0.05&limit=20"Example response
1{
2 "data": [
3 {
4 "id": "9d3e7a18-b426-4c21-8f5e-2c1b4d8a0f12",
5 "kind": "Recommendation",
6 "metadata": {
7 "recommendation_type": "workload_rightsizing",
8 "resource_type": "Deployment",
9 "resource_name": "cart-service",
10 "resource_uid": "b9f1a342-7c5e-4d8a-9e2b-1234567890ab",
11 "cluster": {
12 "id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
13 "name": "prod-eu-west-1"
14 },
15 "namespace": "payments",
16 "title": "Reduce CPU and memory requests on cart-service",
17 "description": "Average CPU utilization is 18% of requests over the last 14 days. Memory utilization is 41%. Cut requests to bring the workload into the 60-80% target band.",
18 "cause": "Requests set well above measured usage. P95 CPU is 0.42 cores against a request of 2.0 cores.",
19 "risk_level": "low",
20 "priority": "high",
21 "status": "pending",
22 "data_points_analyzed": 20160,
23 "created_at": "2026-05-21T03:14:00Z",
24 "updated_at": "2026-05-21T03:14:00Z"
25 },
26 "current": {
27 "config": {
28 "containers": [
29 {
30 "name": "cart-service",
31 "requests": { "cpu": "2.0", "memory": "4Gi" },
32 "limits": { "cpu": "4.0", "memory": "8Gi" }
33 }
34 ]
35 },
36 "hourly_cost": { "amount": "1.8400", "currency": "USD" }
37 },
38 "recommended": {
39 "config": {
40 "containers": [
41 {
42 "name": "cart-service",
43 "requests": { "cpu": "0.6", "memory": "2Gi" },
44 "limits": { "cpu": "1.5", "memory": "4Gi" }
45 }
46 ]
47 }
48 },
49 "applied": {
50 "config": {}
51 },
52 "savings": {
53 "estimated_hourly": { "amount": "0.6200", "currency": "USD" }
54 }
55 }
56 ],
57 "meta": {
58 "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9F",
59 "applied_at": "2026-05-21T10:30:00Z",
60 "pagination": {
61 "next_cursor": "eyJ2IjoxLCJhZnRlcl9pZCI6IjRmOGMyYTkxLTdkM2UtNGIxNi05YThmLTZlNWQ0YzNiMmExMCIsImFmdGVyX3ZhbHVlIjoiMC4yMTAwIiwicXVlcnlfaGFzaCI6ImEzYjRjNWQ2ZTdmOGc5YTBiMWMyZDNlNGY1ZzYiLCJpc3N1ZWRfYXQiOjE3MTYyODUwMDB9",
62 "has_more": true,
63 "limit": 20
64 }
65 }
66}Example error response
1{
2 "data": null,
3 "meta": {
4 "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9G",
5 "applied_at": "2026-05-21T10:30: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_accepted_on_this_endpoint" }
12 ]
13 }
14}Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | Malformed limit, malformed min_savings_hourly, malformed cursor. |
| 400 | INVALID_CLUSTER_ID | A cluster_id value in the filter is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. See Authentication. |
| 403 | FORBIDDEN | Token lacks recommendations:read. See Permission Scopes. |
| 403 | CLUSTER_ACCESS_DENIED | Token's cluster allowlist excludes a cluster_id in the filter. |
| 410 | CURSOR_EXPIRED | Cursor older than 24h or query parameters changed since issue. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. Drop it. |
| 422 | VALIDATION_ERROR | A recommendation_type, status, risk_level, priority, or resource_type value is outside the allowed enum. The response details echoes the field name and the full allowed set. |
| 429 | RATE_LIMITED | Per-key quota exceeded. See Retry-After header. See Error Handling. |
Get recommendation by ID
GET /v1/recommendations/{rec_id}
Required scope: recommendations:read
Detail endpoint. Returns the full recommendation body and adds a metrics_snapshot block carrying the analysis window's input data.
The endpoint is tenant-scoped and respects the API key's cluster allowlist. A recommendation belonging to a cluster the key cannot see returns 403 CLUSTER_ACCESS_DENIED rather than 404, preventing existence probes against unreachable clusters.
Path parameters
| Name | Type | Description |
|---|---|---|
rec_id | UUID | Recommendation identifier returned by the list endpoint. |
Example request
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://public-api.kubeadapt.io/v1/recommendations/9d3e7a18-b426-4c21-8f5e-2c1b4d8a0f12"Example response
Returns a single Recommendation object. Same shape as one element of the List recommendations response above, wrapped directly under data (no array, no pagination in meta). This endpoint adds one top-level field not present on the list: metrics_snapshot, an open-ended object holding the analysis window's input data — CPU/memory usage percentiles, sample count, replica averages, restart and OOM counts. Treat it as an opaque object; additional fields may appear without an API version bump.
1{
2 "data": {
3 "id": "9d3e7a18-b426-4c21-8f5e-2c1b4d8a0f12",
4 "kind": "Recommendation",
5 "metadata": { "recommendation_type": "workload_rightsizing", "resource_name": "cart-service", "namespace": "payments", "priority": "high", "status": "pending", "...": "..." },
6 "current": { "config": { "containers": [{ "name": "cart-service", "requests": { "cpu": "2.0", "memory": "4Gi" }, "limits": { "cpu": "4.0", "memory": "8Gi" } }] }, "hourly_cost": { "amount": "1.8400", "currency": "USD" } },
7 "recommended": { "config": { "containers": [{ "name": "cart-service", "requests": { "cpu": "0.6", "memory": "2Gi" }, "limits": { "cpu": "1.5", "memory": "4Gi" } }] } },
8 "applied": { "config": {} },
9 "savings": { "estimated_hourly": { "amount": "0.6200", "currency": "USD" } },
10 "metrics_snapshot": {
11 "window": { "start_at": "2026-05-07T00:00:00Z", "end_at": "2026-05-21T00:00:00Z", "samples": 20160 },
12 "cpu": { "request_cores": 2.0, "used_cores_p50": 0.28, "used_cores_p95": 0.42, "utilization_percent_p95": 21.0, "...": "..." },
13 "memory": { "request_bytes": 4294967296, "used_bytes_p95": 2147483648, "utilization_percent_p95": 50.0, "...": "..." },
14 "replicas": { "desired_avg": 3.0, "running_avg": 3.0 },
15 "restarts_total": 0,
16 "oom_kills_total": 0
17 }
18 },
19 "meta": {
20 "request_id": "req_01J5K3V0Q7Y4XR8A2B3C5D7E9H",
21 "applied_at": "2026-05-21T10:30:00Z"
22 }
23}For the complete field list (excluding metrics_snapshot), see the canonical example in List recommendations or the OpenAPI spec.
Common errors
| HTTP | error.code | When |
|---|---|---|
| 400 | BAD_REQUEST | rec_id is not a valid UUID. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | Token lacks recommendations:read. |
| 403 | CLUSTER_ACCESS_DENIED | Recommendation belongs to a cluster outside the key's allowlist. |
| 404 | RECOMMENDATION_NOT_FOUND | No recommendation with this ID exists in the tenant. |
| 422 | INVALID_COST_MODE | ?cost_mode= was supplied. Drop it. |
| 429 | RATE_LIMITED | Per-key quota exceeded. |
Field notes
current.hourly_costis the live cost of the workload at the moment the analysis ran. Pair it withsavings.estimated_hourlyto compute the projected post-apply rate (current.hourly_cost - savings.estimated_hourly).applied.configis{}whilestatusispendingordismissed. Whenstatusflips toapplied, the field records the config that was actually applied (may differ fromrecommended.configon a partial apply).priorityis assigned from estimated monthly savings:high(≥ $50/mo),medium(≥ $10/mo),low(otherwise). Read-only.data_points_analyzedis the count of one-minute samples in the analysis window. Fourteen days at one sample per minute is 20,160.metrics_snapshotis a free-form object on the detail endpoint. Read fields from it defensively; new fields ship without bumping the API version.
Filtering patterns
GET /v1/recommendations # default triage (pending + dismissed)
GET /v1/recommendations?risk_level=low&min_savings_hourly=0.10 # safe wins
GET /v1/recommendations?status=pending,applied,dismissed,archived # all statuses
GET /v1/recommendations?status=applied&priority=high,medium # applied wins
GET /v1/recommendations?workload_uid={uid}&status=pending,applied,dismissed,archived # one workload over timeSee also
- Rightsizing: the methodology behind
workload_rightsizing. - Workloads: the resource each recommendation targets.
- Cost Modes: why this endpoint rejects
?cost_mode=. - Cost Explorer: aggregate the cost of recommended-vs-current configurations by workload, namespace, or cluster.