# Read usage across tenants

GET

/v1/system/usage

```sh
curl --request GET \
  --url 'https://example.com/v1/system/usage?from=2026-04-15T12%3A00%3A00Z&to=2026-04-15T12%3A00%3A00Z' \
  --header 'Authorization: Bearer <token>'
```

Usage records aggregated by the dimensions in `groupBy`: each group’s count, the sum of its quantities, and its first and last times. Never a record’s content. Records are counted when the counted thing happens, exactly once (the kinds `kind` lists, each run with its class). `watermark`: every record at or before it is in the answer, so re-read a window until its watermark is past the window’s end; every page of one read carries the same watermark. A test tenant’s usage, and one whose setup is pending, are never in the answer, even when `tenantId` names one. Takes `system:usage:read`; the access record marks a read across every tenant, or of the one named. A runtime that doesn’t count usage (`KINDGI_USAGE_METERING` unset or `off`) answers 404.

## Authorizations

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

## Parameters

### Query Parameters

**from**

required

string format: date-time

Records at or after this time: a time with a zone, such as `2026-11-01T00:00:00Z`.

**to**

required

string format: date-time

Records before this time. At most 92 days after `from`.

**groupBy**

string

Comma-separated, each once: `tenant`, `project`, `agent`, `kind`, `class`, `state`, `day` (the UTC date). Default `tenant,kind`.

**kind**

string

Only these kinds, comma-separated (`run.started`, `run.ended`, `approval.requested`, `approval.decided`, `llm.inference`, `llm.embedding`, `project.production`).

**class**

string

Only these run classes, comma-separated (`production`, `staging`, `dry-run`, `replay`, `eval`, `child`).

**state**

string

Only these states, comma-separated: `on`, `off` (`project.production`); `completed`, `failed`, `cancelled` (`run.ended`); `escalation` (`approval.requested`, an escalation’s next tier); `approved`, `rejected`, `withdrawn`, `escalated`, `expired` (`approval.decided`).

**limit**

integer

\>= 1 <= 10000

Groups a page: 1 to 10000, default 1000.

**cursor**

string

The previous page’s `nextCursor`.

**tenantId**

string format: uuid

Only this tenant.

## Responses

### 200

A page of groups.

application/json

object

**data**

required

Array\<object>

One group: the dimensions it’s grouped by (only those), then its count, the sum of its quantities, and its first and last times.

object

**tenantId**

The tenant, on the operator’s read across tenants.

string format: uuid

**projectId**

Absent for a record with no project; a tenant admin’s read includes those, and the CLI shows them as `(none)`.

string

**agentId**

Absent for a record with no agent; the CLI shows those as `(none)`.

string

**kind**

What was counted: `run.started`, `run.ended`, `approval.requested`, `approval.decided`, `llm.inference`, `llm.embedding`, `project.production`. A runtime may count more kinds later: show one you don’t know as it is.

string

**class**

The class of the run a record counts or belongs to, fixed when the run was created: `production`, `staging`, `dry-run`, `replay`, `eval`, `child`. A record with no counted run has none. A runtime may add classes later: show one you don’t know as it is.

string

**state**

The record’s state, by kind: `on`, `off` (`project.production`); `completed`, `failed`, `cancelled` (`run.ended`); `escalation` (`approval.requested`, an escalation’s next tier); `approved`, `rejected`, `withdrawn`, `escalated`, `expired` (`approval.decided`). Other kinds have none.

string

**day**

The UTC date, `YYYY-MM-DD`.

string

/^\d{4}-\d{2}-\d{2}$/

**count**

required

Records in the group.

integer

**quantity**

required

The sum of their quantities: milliseconds for `run.ended`, tokens for model calls, and 1 for every other record, so a group of those has a quantity equal to its count.

number

**firstAt**

required

string format: date-time

**lastAt**

required

string format: date-time

**watermark**

required

Every record at or before this time is in the answer; a later one may not be yet. Re-read a window until its watermark is past the window’s end. Every page of one read carries the same watermark.

string format: date-time

**nextCursor**

More groups: pass it as `cursor`. Absent on the last page.

string

##### Examplegenerated

```json
{
  "data": [
    {
      "tenantId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
      "projectId": "example",
      "agentId": "example",
      "kind": "example",
      "class": "example",
      "state": "example",
      "day": "example",
      "count": 1,
      "quantity": 1,
      "firstAt": "2026-04-15T12:00:00Z",
      "lastAt": "2026-04-15T12:00:00Z"
    }
  ],
  "watermark": "2026-04-15T12:00:00Z",
  "nextCursor": "example"
}
```

### 400

A window, filter, `limit` or `cursor` this route can’t read (`bad-input`), naming which.

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"
  }
}
```

### 403

A tenant’s key or session (`system-principal-required`), or a system key without `system:usage:read` (`system-scope-missing`).

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

- [Kindgi 0.1.10](https://docs.kindgi.com/releases/0.1.10.md)
- [Error code `bad-input`](https://docs.kindgi.com/reference/errors.md#bad-input)
- [Error code `system-principal-required`](https://docs.kindgi.com/reference/errors.md#system-principal-required)
- [Error code `system-scope-missing`](https://docs.kindgi.com/reference/errors.md#system-scope-missing)
- [Every error code](https://docs.kindgi.com/reference/errors.md)
