Usage
Per-key sparklines, per-tenant counts and the billable-tenant figure.
packages/api/src/resources/usage.controller.ts by packages/docs/tools/generate-reference.mjs. Do not edit by hand: src/reference.spec.ts regenerates it and fails on a difference.What the dashboard reads about usage, and nothing else reads.
The whole file is GET, and that is the point: the two figures here are
written by week 5's rollup and by the ingest path, and a second writer to
either is what migration 0008's header calls "how an invoice becomes wrong".
The division between the two endpoints is the one that matters:
billingiskeyring.billable_tenant_monthread straight through. That view is the single definition of the billing unit -- one customer organisation per workspace, live only, counted across projects -- and it is deliberately not recomputed here fromtenant_activity_month, which is the same mistake in a different building.tenantsreportsrequest_countper tenant, project and environment. Those are dimensions the rollup keeps so per-project and per-environment usage stays available without changing what is billed, so this endpoint carries no billable flag and no total of its own. A caller that wants the number on the invoice asks the other endpoint.
GET /v1/keys/:id/usage
The per-key sparkline, report section 11.3's week-6 row, on the vendor
plane. The embed plane has its own (TenantScopedRepository.dailyUsage)
and the two are deliberately separate statements: that one carries four
predicates bound from a signed token and has no argument a handler could
widen, which is the property embed-isolation.spec.ts asserts. Sharing a
query builder between the two planes would be the one change that could
take it away.
sample_rate is divided out for the same reason it is there: a chart that
counted rows would under-report a sampled fleet by exactly the sampling
factor.
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Path parameters. id.
Query. Validated by this schema, from the control plane's own source:
const KeyUsage = z.object({
days: z.coerce.number().int().min(1).max(90).default(30),
});GET /v1/usage/billing
The view this reads is live-only by definition, so a test credential is
refused rather than filtered; assertBillingReadable carries the reasoning.
month is formatted in SQL for the same reason the sparkline's day is:
node-pg parses a Postgres date into a Date at local midnight, so
.toISOString() on any host east of UTC rolls the label back a day -- and
since the value is always the first of a month, back a whole month. Every EU
host that does not set TZ=UTC is east of it.
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Query. Validated by this schema, from the control plane's own source:
const Billing = z.object({
months: z.coerce.number().int().min(1).max(24).default(6),
});GET /v1/usage/tenants
Answers 200 on success.
Authentication.
- A
krsk_secret key or akrses_dashboard session, asAuthorization: Bearer.
Query. Validated by this schema, from the control plane's own source:
const TenantUsage = z.object({
months: z.coerce.number().int().min(1).max(24).default(1),
env: Env.optional(),
});Shared validators
Defined once in packages/api/src/validation.ts and used by the schemas above.
const Env = z.enum(['live', 'test']);