# Aggregate cost across a time window

GET

/v1/cost/aggregate

```sh
curl --request GET \
  --url 'https://example.com/v1/cost/aggregate?groupBy=example&limit=1000&includeDescendants=false&scopeKind=tenant&inherit=true' \
  --header 'Authorization: Bearer <token>'
```

Primary consumer path for dashboards. `groupBy` is required (comma-separated dimensions from the closed set); time range is required (both `from` and `to`, or both omitted for the default last-30-days window echoed back in `timeRange`). Filters compose on top of the time window. `?scopeKind + ?scopeId` narrow the aggregate to a scope: `org` covers every project in the org, so one call sums an org’s spend. It needs `read` on the scope, and across projects (no scope, the tenant, or an org) it counts only the projects the caller may read, and records with no project; a tenant admin’s counts every project. Each group, and the total, carries its cost and its token sums (`tokens`). `groups` is ordered by `totalUsd`, highest first (ties by key), and capped at `limit` (default 1000): `truncated` and `totalGroups` say when there were more, and the totals still cover every record. For every record, page through `/v1/cost/records`. `inherit` has no effect on cost records, which always belong to a project.

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.1.8/reference/api.md#bearerauth)**

## Parameters

### Query Parameters

**groupBy**

required

string

Comma-separated list of dimensions to aggregate over. Each value must be one of `agentId | runId | category | providerId | day | month | tenant | conversationId | model | servedModel | projectId | orgId | rootRunId | flowId`. `model` is the model actually called; `servedModel` the exact version the vendor reported; `orgId` the org of the record’s project. Duplicates collapse.

**limit**

integer

default: 1000 >= 1 <= 10000

The most groups to return: the most expensive ones (`groups` is ordered by `totalUsd`, highest first). 1 to 10000, default 1000. When there were more, `truncated` is `true` and `totalGroups` says how many; the totals still cover every record.

**from**

string format: date-time

ISO 8601 timestamp; records with `occurredAt >= from`. Required on `/v1/cost/aggregate` (or both endpoints omitted for default last-30-days window).

**to**

string format: date-time

ISO 8601 timestamp; records with `occurredAt < to` (exclusive). Required on `/v1/cost/aggregate` (or both endpoints omitted for default last-30-days window).

**category**

string

Filter to a specific category (`llm.inference`, `tool.invocation`, `storage.write`, `sandbox.exec`, …). Exact match.

**providerId**

string

Filter records to this provider id (exact match).

**agentId**

string

Filter records to this agent id (exact match).

**runId**

string format: uuid

Filter records to this run id (exact match).

**conversationId**

string format: uuid

Filter records to this conversation id (exact match).

**model**

string

Filter model calls to this model, the one actually called (exact match).

**servedModel**

string

Filter model calls to the exact model version the vendor reported (exact match).

**rootRunId**

string format: uuid

Every record of the run tree whose root is this run: a flow run and the agent turns and sub-flows it started.

**includeDescendants**

boolean

With `runId`: the run’s records and those of every run it started, at any depth. `true` or `false` (default).

**scopeKind**

Discriminator for the ?scopeKind + ?scopeId + ?inherit triplet. Tenant carries no id (implicit from session); org/project require scopeId.

string

Allowed values: tenant org project

Optional scope discriminator. If absent, no scope filter is applied.

**scopeId**

string

\>= 1 characters

Required IF scopeKind is `org` or `project`. MUST be absent if scopeKind=`tenant` (tenant is implicit from the session). Malformed combinations return 400 scope-invalid.

**inherit**

boolean

default: true

Default `true`. `false` = literal-at-this-scope only (admin/audit view). Load-bearing for policy/config-scoped resources (mcp-endpoints); documented no-op for content-scoped resources (agents/flows/tools/…).

## Responses

### 200

Aggregate rollup.

application/json

object

**groups**

required

The most expensive groups first (`totalUsd` descending, ties by key), at most `limit`.

Array\<object>

object

**key**

required

One entry per requested `groupBy` dimension. `null` = distinct “unattributed” bucket (records had no value for that dimension).

object

**_key_**

additional properties

string | null

**count**

required

integer

**totalUsd**

required

number

**tokens**

required

object

**prompt**

required

integer

**completion**

required

integer

**cacheRead**

required

integer

**cacheWrite**

required

integer

**reasoning**

required

integer

**totalGroups**

How many groups there were before the `limit` cap. Absent from a runtime before 0.1.5.

integer

**truncated**

`true` when there were more groups than `limit`: `groups` holds the most expensive ones, and `totalUsd` / `totalRecords` / `tokens` still cover every record. Absent from a runtime before 0.1.5.

boolean

**totalUsd**

required

number

**totalRecords**

required

integer

**tokens**

required

object

**prompt**

required

integer

**completion**

required

integer

**cacheRead**

required

integer

**cacheWrite**

required

integer

**reasoning**

required

integer

**timeRange**

required

object

**from**

required

string format: date-time

**to**

required

string format: date-time

**groupBy**

required

The dimensions the server actually grouped by — echoed so callers can round-trip the response without re-parsing the request URL.

Array\<string>

Allowed values: agentId runId category providerId day month tenant conversationId model servedModel projectId orgId rootRunId flowId

##### Example

```json
{
  "groupBy": [
    "agentId"
  ]
}
```

### 400

Missing / malformed `groupBy`, unknown dimension, missing one endpoint of the time range, or `from > to`.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [Cost per run and per customer](https://docs.kindgi.com/v0.1.8/guides/observability/cost-per-run.md)
- [Every error code](https://docs.kindgi.com/v0.1.8/reference/errors.md)
