Keyring

Usage

Per-key sparklines, per-tenant counts and the billable-tenant figure.

Generated from packages/api/src/resources/usage.controller.ts by packages/docs/tools/generate-reference.mjs. Every example is a real exchange recorded against the control plane by packages/api/src/testing/reference-examples.spec.ts, with ids from that run. Credentials in the formats this product issues (kr_, krsk_, krses_, JWTs) and values under credential-named fields (password, *_secret, *_token, *_key, lookup_hash) are elided at record time; the recorded file and every page of this site are swept for credential shapes (kr_, krsk_, krses_, JWTs, URLs carrying a password, password fields) before either is accepted. 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:

  • billing is keyring.billable_tenant_month read 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 from tenant_activity_month, which is the same mistake in a different building.
  • tenants reports request_count per 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 a krses_ dashboard session, as Authorization: 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),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/keys/01a0ade7-b916-78a6-801e-79f4ba8233fb/usage?days=7 \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "usage",
  "key_id": "01a0ade7-b916-78a6-801e-79f4ba8233fb",
  "days": 7,
  "data": [
    {
      "day": "2026-09-17",
      "requests": 2,
      "errors": 0
    }
  ]
}

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 a krses_ dashboard session, as Authorization: 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),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/usage/billing?months=3 \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "list",
  "data": [
    {
      "object": "billable_tenant_month",
      "month": "2026-09-01",
      "active_tenants": 1
    }
  ]
}

GET /v1/usage/tenants

Answers 200 on success.

Authentication.

  • A krsk_ secret key or a krses_ dashboard session, as Authorization: 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(),
});

Example.

Request
curl https://keyring-api.belghalem.fr/v1/usage/tenants?months=1 \
  -H "Authorization: Bearer $KEYRING_SECRET_KEY"
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "object": "list",
  "data": [
    {
      "object": "tenant_activity_month",
      "tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
      "project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
      "env": "test",
      "month": "2026-09-01",
      "request_count": 2,
      "first_seen_at": "2026-09-17T05:47:20.053Z",
      "last_seen_at": "2026-09-17T05:47:20.053Z"
    },
    {
      "object": "tenant_activity_month",
      "tenant_id": "01a0ade7-b909-7ae2-9477-aa287f446aad",
      "project_id": "01a0ade7-b8fb-7711-bb1a-7d1bd5962995",
      "env": "live",
      "month": "2026-09-01",
      "request_count": 1,
      "first_seen_at": "2026-09-17T05:47:20.053Z",
      "last_seen_at": "2026-09-17T05:47:20.053Z"
    }
  ]
}

Shared validators

Defined once in packages/api/src/validation.ts and used by the schemas above.

const Env = z.enum(['live', 'test']);

On this page