API Reference
Permission Scopes
The nine Kubeadapt API read scopes, the endpoints each one covers, the cluster ACL model, the 403 details payload, and minimum-token recipes.
Last updated
Every API key encodes one or more of the nine *:read scopes — there are no write, admin, or break-glass scopes. Scopes gate endpoint families; a separate cluster ACL on the same key narrows access to a subset of clusters within those endpoints. Operations that would require write access (creating teams, applying recommendations, rotating cluster credentials) live in the Kubeadapt dashboard, not the API.
The nine read scopes
organization:read
The tenant-level view: total clusters, total spend, month-to-date billed cost, savings potential. The dashboard endpoint is the only place where MTD and savings figures live.
Endpoints:
GET /v1/organization: the snapshot. Aggregate capacity, utilization, and cost across allowed clusters. Rejects?cost_mode=(the org-level bill is one physical number).GET /v1/organization/dashboard: the dashboard. Month-to-date billed cost, savings potential (current hourly potential + recommendation count), top-N clusters by month-to-date cost. Accepts?cost_mode=and returns genuinely different numbers per mode.
clusters:read
Cluster inventory: enumerate clusters, look one up by ID, list cluster status.
Endpoints:
GET /v1/clusters: paginated list of clusters in the key's allow-list. Filters:provider,region,environment,status. Default sort bycost.current_run_rate_hourly DESC. Rejects?cost_mode=.GET /v1/clusters/{cluster_id}: single cluster body. Rejects?cost_mode=.
namespaces:read
Kubernetes namespaces with namespace-level cost totals, optionally filtered by cluster.
Endpoints:
GET /v1/namespaces: paginated cross-cluster list.GET /v1/clusters/{cluster_id}/namespaces: paginated list scoped to one cluster.GET /v1/clusters/{cluster_id}/namespaces/{namespace}: single namespace body.
All three accept ?cost_mode=. The response echoes the requested mode in meta.cost_mode and cost.cost_mode, and the two modes return different amounts: fully_loaded includes the namespace's share of idle capacity and cluster overhead, workload_only does not.
workloads:read
Deployments, StatefulSets, DaemonSets, and the pod sub-list scoped to a workload. The controller-granularity surface used by chargeback pipelines, anomaly detection, and rightsizing dashboards.
Endpoints:
GET /v1/workloads: paginated cross-cluster list. Filter?kind=to one or more ofDeployment,StatefulSet,DaemonSet.GET /v1/clusters/{cluster_id}/workloads: paginated list scoped to one cluster.GET /v1/workloads/{workload_uid}: single workload body.GET /v1/workloads/{workload_uid}/pods: paginated pod list for the workload.
All four accept ?cost_mode=. Same as namespaces: the response echoes the requested mode, and the two modes return different amounts.
nodes:read
Kubernetes nodes plus the node-group aggregates derived from them. Both families share this scope; there is no separate node-groups:read.
Endpoints:
GET /v1/nodes: paginated cross-cluster list.GET /v1/clusters/{cluster_id}/nodes: paginated list scoped to one cluster.GET /v1/nodes/{node_uid}: single node body.GET /v1/node-groups: cross-cluster node-group aggregates. Not paginated.GET /v1/clusters/{cluster_id}/node-groups: node groups in one cluster. Not paginated.GET /v1/clusters/{cluster_id}/node-groups/{name}: single node-group body.
Every node and node-group endpoint rejects ?cost_mode= with 422 INVALID_COST_MODE. Nodes are physical infrastructure; their cost is one number, not a mode choice.
recommendations:read
Cost and efficiency recommendations with savings projections.
Endpoints:
GET /v1/recommendations: paginated list. Filters:cluster_id,namespace,recommendation_type(public API surfaces onlyworkload_rightsizing),status(pending,applied,dismissed,archived),risk_level(low,medium,high),priority(low,medium,high),resource_type(Deployment,StatefulSet,DaemonSet,Pod,Node),workload_uid,min_savings_hourly.GET /v1/recommendations/{rec_id}: single recommendation body, including ametrics_snapshotfield.
Both endpoints reject ?cost_mode=. Recommendation savings use a fixed pricing model and are mode-agnostic.
teams:read
Team identity and the workload assignments that attach cost to teams. The scope a finance chargeback pipeline requires.
Endpoints:
GET /v1/teams: paginated team list with each team's hourly run rate over the last seven complete days (not a month-to-date figure).GET /v1/teams/{team_id}: single team body.GET /v1/teams/{team_id}/assignments: paginated list of the workloads attributed to this team.
The list and detail endpoints accept ?cost_mode= and return the cost at the requested mode. The assignments endpoint carries no cost and rejects ?cost_mode= and ?basis= with 422.
departments:read
Department identity and total department cost. Departments sit one level above teams in the attribution hierarchy.
Endpoints:
GET /v1/departments: paginated department list.GET /v1/departments/{dept_id}: single department body, including child team summaries.
Both accept ?cost_mode= and return the cost at the requested mode.
cost_explorer:read
Aggregated cost queries with grouping, filtering, and KPI summaries via Cost Explorer.
Endpoints:
POST /v1/cost-explorer/query: paginated cost rows + summary KPIs. The onlyPOSTin/v1. Uses offset pagination (page/per_page) and takescost_modeas a field in the JSON body. A?cost_mode=query parameter is ignored.GET /v1/exports/focus: the FOCUS 1.4 cost export, streamed as CSV, JSON Lines or Parquet. See Exports.GET /v1/exports/focus/schema: the metadata object that describes an export configuration.
cost_explorer:read does not imply or substitute for any other scope; if your integration needs the Cost Explorer view and the per-resource endpoints, mint a key with both.
The scope-to-endpoint table
For copy-paste into your integration's docs:
| Scope | Endpoints |
|---|---|
organization:read | /v1/organization, /v1/organization/dashboard, /v1/organizations/dashboard/cost-trend |
clusters:read | /v1/clusters, /v1/clusters/{id}, /v1/clusters/{id}/cost-trend |
namespaces:read | /v1/namespaces, /v1/clusters/{id}/namespaces, /v1/clusters/{id}/namespaces/{ns}, /v1/clusters/{id}/namespaces/{ns}/cost-trend |
workloads:read | /v1/workloads, /v1/clusters/{id}/workloads, /v1/workloads/{uid}, /v1/workloads/{uid}/pods, /v1/clusters/{id}/workloads/by-uid/{uid}/cost-trend |
nodes:read | /v1/nodes, /v1/clusters/{id}/nodes, /v1/nodes/{uid}, /v1/node-groups, /v1/clusters/{id}/node-groups, /v1/clusters/{id}/node-groups/{name}, /v1/clusters/{id}/node-groups/{name}/cost-trend |
recommendations:read | /v1/recommendations, /v1/recommendations/{id} |
teams:read | /v1/teams, /v1/teams/{id}, /v1/teams/{id}/assignments |
departments:read | /v1/departments, /v1/departments/{id} |
cost_explorer:read | /v1/cost-explorer/query, /v1/exports/focus, /v1/exports/focus/schema |
A request to an endpoint not covered by any of your key's scopes returns 403 FORBIDDEN. error.details[0].required lists the scopes the endpoint accepts and error.details[0].granted lists the key's scopes.
Cluster ACLs
Scopes are the first axis of authorization. Cluster ACLs are the second.
A key minted with clusters:read and an allow-list of three cluster UUIDs sees only those three clusters: GET /v1/clusters returns the three, GET /v1/clusters/{cluster_id} for a fourth returns 403 CLUSTER_ACCESS_DENIED. The allow-list applies to every cluster-scoped path, regardless of which scope opens it. Asking for a workload in a forbidden cluster, a namespace in a forbidden cluster, or a node in a forbidden cluster all return 403.
The allow-list is tri-state (nil, [], [ids]) — see Authentication § The cluster ACL tri-state for the full table.
Cross-cluster list endpoints (/v1/clusters, /v1/workloads, /v1/namespaces, etc.) return only the clusters the API key can access; they do not return 403 on a mismatch. The 403 path triggers only when an integration asks for a specific cluster that is not permitted: in the path, in a cluster_id filter, or in Cost Explorer cluster_ids (a query whose cluster_ids are all outside the allow-list returns 403).
Keys created in the dashboard cover every cluster (nil). A per-cluster allow-list can currently only be set by support.
The 403 details payload
Branch on error.code. FORBIDDEN carries the scope detail in error.details[0]; CLUSTER_ACCESS_DENIED carries no details.
For FORBIDDEN (missing scope):
1{
2 "data": null,
3 "meta": {
4 "request_id": "0196f1c2-fa84-78c9-a64c-255a2a54f9b9",
5 "applied_at": "2026-05-21T10:30:00Z"
6 },
7 "error": {
8 "code": "FORBIDDEN",
9 "message": "insufficient scope",
10 "details": [{ "required": ["clusters:read"], "granted": ["organization:read"] }]
11 }
12}For CLUSTER_ACCESS_DENIED (blocked cluster), there is no details array; the cluster is the one in your request:
1{
2 "data": null,
3 "meta": {
4 "request_id": "0196f1c2-e1ee-790e-bb17-0f9d3b8ed5cd",
5 "applied_at": "2026-05-21T10:30:01Z"
6 },
7 "error": {
8 "code": "CLUSTER_ACCESS_DENIED",
9 "message": "access to this cluster is not allowed"
10 }
11}Minimum-token recipes
Three concrete starting points for common integrations. Each picks the smallest scope set that gets the job done.
Read-only dashboard exporter
Pulls cluster, namespace, workload, and node data into your own dashboarding stack (Grafana, Looker, Tableau, Metabase). Reads org-level summary for headline numbers.
Scopes:
organization:readclusters:readnamespaces:readworkloads:readnodes:read
Cluster ACL: nil (all clusters), which is what a dashboard-created key gets. If the exporter should see only some clusters, ask support to set an allow-list.
Finance chargeback pipeline
Runs monthly. Pulls cost-by-team and cost-by-department, plus the org-level total for reconciliation. Does not touch infrastructure resources.
Scopes:
teams:readdepartments:readorganization:read
Cluster ACL: nil. Chargeback aggregates across the whole tenant.
SRE savings dashboard
Surfaces optimization opportunities for a platform team. Lists active recommendations, filters by cluster, drills into nodes for the rightsizing details.
Scopes:
recommendations:readclusters:readnodes:read
Cluster ACL: nil for an org-wide view. To limit the key to production clusters, ask support to set an allow-list.
Anti-patterns
A few patterns to avoid because they create operational pain later:
- One omnibus key with every scope, used by everything. When something goes wrong (leak, compromised integration, key rotation) you have to coordinate across every consumer at once. Mint per integration.
- Scopes a key "might need later". Speculative permissions widen the blast radius of a leak. Mint with the minimum scope set; mint a second key when the additional scope is actually needed.
- An org-wide key for an integration that only needs a few clusters. If the integration is logically scoped to a subset of clusters (per-BU exporter, per-region pipeline), ask support to set an allow-list on its key, so a new cluster doesn't join the export by default.
See also
- Authentication: the Bearer token format, minting and rotation, the 401 vs 403 distinction.
- API Overview: base URL, envelope shape, rate limits, the resource-family index.
- Error Handling: the full error code catalog including
FORBIDDENandCLUSTER_ACCESS_DENIED.