Skip to content

API Reference

Exports

Download a FOCUS 1.4 cost export as CSV, JSON Lines or Parquet, and read the schema that describes it. Parameters, limits and errors.

Last updated


The export endpoints return your cost data as a FOCUS 1.4 Cost and Usage file, the same file the Kubeadapt dashboard produces. Use them to load Kubernetes cost into a data warehouse or a FinOps tool that reads FOCUS.

Endpoints

EndpointReturnsRequired scope
GET /v1/exports/focusThe export file, streamedcost_explorer:read
GET /v1/exports/focus/schemaThe FOCUS metadata object for one export configuration, as JSON in the standard envelopecost_explorer:read

Export a window

GET /v1/exports/focus

Query parameters

NameRequiredDefaultAllowed valuesDescription
fromyes—YYYY-MM-DD (UTC)First day of the window, inclusive.
toyes—YYYY-MM-DD (UTC)The day after the last day of the window (exclusive). The window can be at most 3,660 days.
formatnocsvcsv, jsonl, parquetFile format.
grainnoworkloadworkload, namespaceOne row per workload or per namespace.
idlenounallocatedunallocated, apportionedunallocated keeps idle capacity on its own rows; apportioned spreads it across the rows that used the node.
columnsnoallcomma-separated FOCUS column namesLimit the file to these columns. An unknown name returns 422.
cluster_idnoevery cluster the key can readUUID, repeatable or comma-separatedLimit the export to these clusters.
basisnoallallThe export always carries every cost component, so basis=legacy returns 422. See Cost basis.

The export takes no cost_mode: use idle to choose how idle capacity is shown. Sending cost_mode with any value returns 422 INVALID_COST_MODE.

Example

Export January 2026 at workload grain as CSV:

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -o kubeadapt-focus-2026-01.csv \
  "https://public-api.kubeadapt.io/v1/exports/focus?from=2026-01-01&to=2026-02-01&format=csv&grain=workload"

Check that the file is complete

The response streams, so the 200 status is sent before the first row. A 200 means the export started, not that it finished.

A file whose download ends normally is complete. If the export fails after the download started, the connection is closed before the end of the file, and your HTTP client reports an incomplete transfer instead of success:

ClientWhat a cut-off export looks like
curlExit code 18 (transfer closed with outstanding read data remaining).
Python requestsrequests.exceptions.ChunkedEncodingError while reading the body.
Go net/httpunexpected EOF from reading resp.Body.

Discard a file whose download failed and request it again. Write to a temporary path and move it into place only after the download succeeds, so a partial file never reaches your warehouse.

Headers sent before the body:

HeaderMeaning
Content-Dispositionattachment, with a file name that names the grain and window.
X-Focus-VersionThe FOCUS version the file follows.
X-Focus-Schema-IdIdentifies the column set. Matches schema_id from the schema endpoint for the same configuration.
X-Focus-Dataset-Instance-IdIdentifies the delivery scope. A later file with the same value replaces the earlier one in full.
X-Export-Partialtrue when the file covers fewer clusters than your organization has, because of the cluster_id filter or the key's cluster allow-list. The schema endpoint's scope.excluded_by_rbac counts the clusters the allow-list leaves out.

Limits

  • 6 requests per minute per API key, on top of the 100 per minute limit. To pull more history, widen the window rather than looping over short ones.
  • Cluster scope. cluster_id is intersected with the key's cluster allow-list. A restricted key that names only clusters outside its list gets 403 CLUSTER_ACCESS_DENIED; the export is never widened to the whole tenant.

Read the schema

GET /v1/exports/focus/schema

Returns the FOCUS metadata object for one configuration: the column definitions, how corrections and delivery are handled, and what the key's cluster scope leaves out. The schema_id it returns is the one stamped on every file produced with the same configuration.

It accepts grain, idle, columns and basis with the same meaning as above, and rejects cost_mode the same way. It does not read from, to or cluster_id: the schema always describes what this key can export. It does not count against the 6-per-minute export limit.

bash
curl -H "Authorization: Bearer ka_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  "https://public-api.kubeadapt.io/v1/exports/focus/schema?grain=workload&idle=unallocated"

Errors

Errors on both endpoints use the standard {data, meta, error} envelope.

HTTPerror.codeCause
400INVALID_CLUSTER_IDA cluster_id value is not a valid UUID.
401UNAUTHORIZEDMissing or invalid Bearer token.
403FORBIDDENAPI key lacks cost_explorer:read.
403CLUSTER_ACCESS_DENIEDNo requested cluster is in the key's allow-list (details[0].reason is no_cluster_in_scope).
422VALIDATION_ERRORfrom or to missing or not YYYY-MM-DD; to not after from; window longer than 3,660 days; unknown format, grain, idle or column; basis=legacy.
422INVALID_COST_MODEThe request carries cost_mode.
429RATE_LIMITEDOver the export limit or the service-wide limit. Honor Retry-After.
500INTERNAL_ERRORUnhandled server error.
502UPSTREAM_UNAVAILABLEThe export could not be produced. Retry with backoff.

See Error Handling for the full catalog.

See also