Skip to content

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

plaintext
POST /v1/cost-explorer/query
Required scopecost_explorer:read
PaginationOffset-based (page / per_page) — distinct from the cursor pagination used elsewhere.
Cost modeAccepted as the cost_mode body field. Default fully_loaded. A ?cost_mode= query parameter is ignored on this route.
Cost basisAccepted 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 cap1 MiB. A larger body returns 400 BAD_REQUEST.
Cluster scopingResults 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.

FieldTypeDefaultDescription
group_byenumworkloadOne 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_modeenumfully_loadedfully_loaded or workload_only. See Cost Modes.
basisenumlegacylegacy or all. See Cost basis.
start_datestring (YYYY-MM-DD)30 days before today (UTC)Inclusive.
end_datestring (YYYY-MM-DD)today (UTC)Inclusive. Must not be before start_date.
pageinteger11-indexed.
per_pageinteger20Values 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_bystringtotalCostOne of totalCost, cpuCost, ramCost, gpuCost, co2Kg, name. Any other value falls back to totalCost without an error.
sort_orderenumdescasc or desc.
searchstring—Substring match against the grouping dimension (workload name, namespace, etc.).
view_modeenumdetaildetail (per-resource rows) or grouped (aggregated rows).
filter_expressionobject—See Filter expression.
cluster_idsstring[]—UUIDs. Omit it (or send []) to query every cluster the API key can access.
projectedbooleanfalseWhen true, scales partial-collection days up to a full 24-hour equivalent. When false, returns the observed cost (matches cloud billing).
exportbooleanfalseRaises the per_page cap to 10,000 and forces page to 1.
granularityenumdailydaily, 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.

json
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 typeFieldOperators
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.

json
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

FieldTypeDescription
idstringStable row identifier within the response.
workloadNamestringResource name at the grouping dimension.
workloadKindstringResource kind (Deployment, StatefulSet, etc.).
namespacestringKubernetes namespace.
clusterstringCluster UUID.
clusterNamestringHuman-readable cluster name.
workloadUidstringKubernetes metadata.uid.
nodeUidstringOptional. Node UID; populated when the grouping dimension exposes it (e.g. group_by=pod, group_by=node).
podUidstringOptional. Pod UID; populated when the grouping dimension exposes it (e.g. group_by=pod, group_by=container).
podsintegerNumber of pods contributing to the row.
totalCostMoney{amount, currency}. amount is a decimal string.
co2KgnumberEstimated emissions for the period, in kg CO₂e.
components, unallocated, basis, partialThe row's cost block fields. A money_state object (per-component pricing status) appears only when reported.

Summary fields

FieldTypeDescription
periodCostnumberTotal cost for the requested window.
previousPeriodCostnumberTotal cost for the equivalent prior window.
changePercentagenumber | 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%.
scannedResourcesintegerDistinct resources touched by the query.
queryTimeMsintegerServer-side query duration.
dataCompletenessnumber[0,1] ratio of observed to expected data points for the window. Values below 0.98 indicate incomplete data for the period.
topNamespacestringNamespace contributing the most cost.
topNamespaceCostnumberCost for topNamespace.
unallocatedCostnumberCost in the window that could not be attributed to any items row.
totalsReconciledbooleantrue 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.
rowsTruncatedbooleantrue 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, previousCostobjectThe 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:

FieldTypeDescription
excludedClusterIdsstring[]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.
basisstringThe basis every total on the response was summed at.
partialbooleantrue 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

HTTPerror.codeCause
400BAD_REQUESTMalformed JSON body, or a body larger than 1 MiB.
401UNAUTHORIZEDMissing or invalid Bearer token.
403FORBIDDENAPI key lacks cost_explorer:read.
403CLUSTER_ACCESS_DENIEDEvery cluster in cluster_ids is outside the key's allow-list.
422VALIDATION_ERRORUnknown 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.
422INVALID_COST_MODEcost_mode is not fully_loaded or workload_only.
429RATE_LIMITEDPer-key quota exceeded. Honor Retry-After.
500INTERNAL_ERRORUnhandled server error.
502UPSTREAM_UNAVAILABLEThe service behind this endpoint returned an error or was unreachable.
504UPSTREAM_TIMEOUTThe 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:

bash
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:read scope and the cluster ACL model.
  • Cost Modes: the fully_loaded vs workload_only contract.
  • Error Handling: the full error code catalog including UPSTREAM_UNAVAILABLE and UPSTREAM_TIMEOUT.