API Reference
Cost Explorer
POST /v1/cost-explorer/query: aggregated cost rows with filters, grouping, and KPI summary. Body schema, filter DSL, response shape, and errors.
Last updated
The Cost Explorer endpoint runs aggregated cost queries against your tenant's billable resources. One endpoint, one method, one body schema, one paginated response.
Endpoint
POST /v1/cost-explorer/query| Required scope | cost_explorer:read |
| Pagination | Offset-based (page / per_page) — distinct from the cursor pagination used elsewhere. |
| Cost mode | Accepted as the cost_mode body field. Default fully_loaded. A ?cost_mode= query parameter is ignored on this route. |
| Cost basis | Accepted as the basis body field or as ?basis=. Default legacy. If both are given they must match, or the request returns 422 VALIDATION_ERROR. |
| Body cap | 1 MiB. A larger body returns 400 BAD_REQUEST. |
| Cluster scoping | Results are scoped to clusters your API key is authorized to access. A request whose cluster_ids are all outside the key's allow-list is refused with 403 CLUSTER_ACCESS_DENIED. |
Request body
All fields are optional. Defaults below apply when a field is omitted.
| Field | Type | Default | Description |
|---|---|---|---|
group_by | enum | workload | One of workload, namespace, controller_kind, pod, container, deployment, statefulset, daemonset, node. The cluster axis is not exposed on the public API — use /v1/organization/dashboard for fleet-wide cost breakdown. instance_type is not a grouping axis; you can still filter on it in filter_expression. |
cost_mode | enum | fully_loaded | fully_loaded or workload_only. See Cost Modes. |
basis | enum | legacy | legacy or all. See Cost basis. |
start_date | string (YYYY-MM-DD) | 30 days before today (UTC) | Inclusive. |
end_date | string (YYYY-MM-DD) | today (UTC) | Inclusive. Must not be before start_date. |
page | integer | 1 | 1-indexed. |
per_page | integer | 20 | Values above 100 are clamped to 100 without an error (to 10000 when export=true, which also forces page to 1). Read data.pagination.perPage for the size actually applied. |
sort_by | string | totalCost | One of totalCost, cpuCost, ramCost, gpuCost, co2Kg, name. Any other value falls back to totalCost without an error. |
sort_order | enum | desc | asc or desc. |
search | string | — | Substring match against the grouping dimension (workload name, namespace, etc.). |
view_mode | enum | detail | detail (per-resource rows) or grouped (aggregated rows). |
filter_expression | object | — | See Filter expression. |
cluster_ids | string[] | — | UUIDs. Omit it (or send []) to query every cluster the API key can access. |
projected | boolean | false | When true, scales partial-collection days up to a full 24-hour equivalent. When false, returns the observed cost (matches cloud billing). |
export | boolean | false | Raises the per_page cap to 10,000 and forces page to 1. |
granularity | enum | daily | daily, weekly, or monthly. Time-bucket for daily-breakdown outputs. |
Filter expression
A filter expression is a set of groups combined with AND or OR. Each group holds one or more field filters and one or more label filters; predicates within a group are always combined with AND.
1{
2 "filter_expression": {
3 "group_operator": "AND",
4 "groups": [
5 {
6 "filters": [
7 { "field": "namespace", "operator": "in", "values": ["prod", "staging"] }
8 ],
9 "label_filters": [
10 { "key": "team", "operator": "eq", "values": ["platform"] }
11 ]
12 }
13 ]
14 }
15}| Filter type | Field | Operators |
|---|---|---|
filters[] | field (any indexed dimension), operator, values[] | eq, ne, in, not_in. For a substring match, use the top-level search field. |
label_filters[] | key (label name), operator, values[] | eq, ne, exists, not_exists (values omitted for exists/not_exists) |
group_operator | — | AND or OR (between groups). |
Response
A successful response uses a different envelope from the rest of /v1: {success, data, request_id}, with no meta block. Errors use the standard {data, meta, error} envelope.
1{
2 "success": true,
3 "data": {
4 "items": [
5 {
6 "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7 "workloadName": "checkout-api",
8 "workloadKind": "Deployment",
9 "namespace": "prod",
10 "cluster": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
11 "clusterName": "prod-us-east",
12 "workloadUid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
13 "pods": 12,
14 "totalCost": { "amount": "412.5300", "currency": "USD" },
15 "co2Kg": 7.4,
16 "components": {
17 "compute.cpu": { "amount": "298.1100", "currency": "USD" },
18 "compute.ram": { "amount": "114.4200", "currency": "USD" }
19 },
20 "unallocated": { "amount": "0.0000", "currency": "USD" },
21 "basis": "legacy",
22 "partial": false
23 }
24 ],
25 "summary": {
26 "periodCost": 412.53,
27 "previousPeriodCost": 387.10,
28 "changePercentage": 6.57,
29 "scannedResources": 248,
30 "queryTimeMs": 142,
31 "dataCompleteness": 0.99,
32 "topNamespace": "prod",
33 "topNamespaceCost": 287.42,
34 "unallocatedCost": 0,
35 "totalsReconciled": true,
36 "rowsTruncated": false
37 },
38 "pagination": {
39 "page": 1,
40 "perPage": 20,
41 "totalCount": 248,
42 "totalPages": 13
43 },
44 "basis": "legacy",
45 "partial": false
46 },
47 "request_id": "0196f1c2-7a3e-7b5d-9c41-2e8f6a1d3b73"
48}Item fields
| Field | Type | Description |
|---|---|---|
id | string | Stable row identifier within the response. |
workloadName | string | Resource name at the grouping dimension. |
workloadKind | string | Resource kind (Deployment, StatefulSet, etc.). |
namespace | string | Kubernetes namespace. |
cluster | string | Cluster UUID. |
clusterName | string | Human-readable cluster name. |
workloadUid | string | Kubernetes metadata.uid. |
nodeUid | string | Optional. Node UID; populated when the grouping dimension exposes it (e.g. group_by=pod, group_by=node). |
podUid | string | Optional. Pod UID; populated when the grouping dimension exposes it (e.g. group_by=pod, group_by=container). |
pods | integer | Number of pods contributing to the row. |
totalCost | Money | {amount, currency}. amount is a decimal string. |
co2Kg | number | Estimated emissions for the period, in kg CO₂e. |
components, unallocated, basis, partial | The row's cost block fields. A money_state object (per-component pricing status) appears only when reported. |
Summary fields
| Field | Type | Description |
|---|---|---|
periodCost | number | Total cost for the requested window. |
previousPeriodCost | number | Total cost for the equivalent prior window. |
changePercentage | number | null | (periodCost − previousPeriodCost) / previousPeriodCost × 100. null when previousPeriodCost is 0 or the window has no comparable prior period — this is distinct from 0, which means "measured, and unchanged". Clients must render null as an empty state, not as 0%. |
scannedResources | integer | Distinct resources touched by the query. |
queryTimeMs | integer | Server-side query duration. |
dataCompleteness | number | [0,1] ratio of observed to expected data points for the window. Values below 0.98 indicate incomplete data for the period. |
topNamespace | string | Namespace contributing the most cost. |
topNamespaceCost | number | Cost for topNamespace. |
unallocatedCost | number | Cost in the window that could not be attributed to any items row. |
totalsReconciled | boolean | true when the grand total for this group_by ties to the cloud bill (workload, namespace, controller_kind, deployment, statefulset, daemonset). false for pod and container, whose totals leave out batch jobs, and for node, whose total leaves out storage and the cluster management fee. |
rowsTruncated | boolean | true when a grouped query summed more rows than it could fetch, so the row figures undercount and can total less than periodCost. Narrow the filters or the window. |
cost, previousCost | object | The component split of periodCost and previousPeriodCost, in the cost block shape. |
Top-level response fields
items, summary, and pagination are documented above. The response also includes:
| Field | Type | Description |
|---|---|---|
excludedClusterIds | string[] | Cluster UUIDs left out of the query because their agent is stale or disconnected. Clusters outside the API key's access do not appear in this list. Omitted when no cluster was excluded. Sits inside data, not inside summary. |
basis | string | The basis every total on the response was summed at. |
partial | boolean | true when some component of the basis could not be produced for every row and total. Always false at basis=legacy. |
At basis=all a response can also carry excludedClusters (clusters whose network cost is missing from every total) and a data_quality object.
Errors
| HTTP | error.code | Cause |
|---|---|---|
| 400 | BAD_REQUEST | Malformed JSON body, or a body larger than 1 MiB. |
| 401 | UNAUTHORIZED | Missing or invalid Bearer token. |
| 403 | FORBIDDEN | API key lacks cost_explorer:read. |
| 403 | CLUSTER_ACCESS_DENIED | Every cluster in cluster_ids is outside the key's allow-list. |
| 422 | VALIDATION_ERROR | Unknown group_by, sort_order, view_mode, granularity or basis; unknown filter operator; start_date/end_date not YYYY-MM-DD; end_date before start_date; negative page or per_page. |
| 422 | INVALID_COST_MODE | cost_mode is not fully_loaded or workload_only. |
| 429 | RATE_LIMITED | Per-key quota exceeded. Honor Retry-After. |
| 500 | INTERNAL_ERROR | Unhandled server error. |
| 502 | UPSTREAM_UNAVAILABLE | The service behind this endpoint returned an error or was unreachable. |
| 504 | UPSTREAM_TIMEOUT | The query did not finish within the 30-second deadline. Narrow the window or the filters, then retry. |
See Error Handling for the full catalog and retry guidance.
Example
Group spend by namespace across two clusters for the first week of May, sorted by total cost descending:
1curl -X POST \
2 -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "group_by": "namespace",
6 "cost_mode": "fully_loaded",
7 "start_date": "2026-05-01",
8 "end_date": "2026-05-07",
9 "page": 1,
10 "per_page": 100,
11 "sort_by": "totalCost",
12 "sort_order": "desc",
13 "cluster_ids": [
14 "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
15 "c2b3c4d5-e6f7-8901-bcde-f23456789012"
16 ]
17 }' \
18 "https://public-api.kubeadapt.io/v1/cost-explorer/query"See also
- API Overview: base URL, envelope shape, the full endpoint index.
- Permission Scopes: the
cost_explorer:readscope and the cluster ACL model. - Cost Modes: the
fully_loadedvsworkload_onlycontract. - Error Handling: the full error code catalog including
UPSTREAM_UNAVAILABLEandUPSTREAM_TIMEOUT.