> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vainona.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Summarize every tenant

> Publish a banded document per tenant namespace into your template's namespace, then judge, query and subscribe to your tenants like any documents.

export const productName = "Vainona";

A platform asks one question no single tenant can answer: across all my tenants, which are turning? Each tenant is a namespace {productName} keeps apart on purpose, so nothing reads across them. A **tenant summary** is the one way up: a [template](/guides/templates) publishes a **tenant document** per namespace under it, into the template's own namespace, built from that tenant's [groups](/guides/groups) and banded. Your platform then defines ordinary judgments, queries and subscriptions over the tenant documents.

Only bands cross the boundary. A judgment over tenant documents reads no tenant's text, only the labels each tenant published. It is the one [relation](/concepts/relations) that crosses namespaces, and only upward.

## Set one up

Say every tenant under `acme/prod/*` has a template group `abuse_by_day`, keyed by a day bucket you write, and `by_plan`. Choose the groups each tenant publishes, and a band for each aggregate you want to judge by:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
POST /v1/namespaces/acme%2Fprod%2F*/tenant_summary
{
  "groups": ["by_plan", "abuse_by_day"],
  "bands": {
    "abuse_by_day.count": {"bands": [10, 100], "labels": ["quiet", "some", "busy"]},
    "by_plan.avg(state.mrr)": {"bands": [50, 500], "labels": ["small", "mid", "large"]}
  }
}
```

Every group must exist on the template, and every key of `bands` names one of their aggregates as `<group>.<aggregate>`, as a group's rows key them. A value takes the label of how many cut points are at or below it, so each band has one more label than cut points. Templates need the Team plan or above.

## The tenant document

Once an hour a run reads each tenant's merged group rows and writes its document into the template's namespace, here `acme/prod`:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "id": "acme/prod/tenant_123",
  "attributes": {"kind": "tenant"},
  "state": {
    "groups": {
      "abuse_by_day": {"2026-09-29": {"count": "busy"}, "2026-09-28": {"count": "some"}},
      "by_plan": {"pro": {"avg(state.mrr)": "mid"}}
    },
    "source": {
      "values": {"abuse_by_day": {"2026-09-29": {"count": 214}}, "by_plan": {"pro": {"count": 38, "avg(state.mrr)": 240.5}}},
      "as_of": 20488,
      "life": "01JB7S2Q4X0M3V6Y9K1D5F8H2N",
      "lease": "01JB7S3..."
    },
    "published_at": "2026-09-29T14:00:03.120Z"
  }
}
```

* `id` is the tenant's namespace name, and `attributes.kind` is `tenant`.
* `state.groups` holds the labels of the aggregates you banded. An aggregate without a band appears only in `state.source.values`.
* `state.source` says where the labels came from: the values, the storage merge they are exact at (`as_of`), the tenant's incarnation (`life`) and the run that wrote them (`lease`).

A run writes a tenant's document only when its labels moved, so a quiet tenant costs nothing to keep current. A tenant with none of the groups merged yet has no document.

## Judge your tenants

A tenant document is an ordinary document, so a judgment reads it like any other:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
POST /v1/namespaces/acme%2Fprod/judgments
{
  "name": "tenant_health",
  "type": "bool",
  "applies_to": {"attributes.kind": "tenant"},
  "question": "Is this community's abuse rising?",
  "context": {"fields": ["state.groups"]},
  "engine": {"name": "jev", "version": "current"},
  "freshness": {"policy": "on_change"}
}
```

It is judged when a tenant's labels move, queried like any judgment, and a [subscription](/concepts/subscriptions) on it tells you when a tenant turns. Its audit stops at the tenant document, whose `state.source` carries the values it was built from.

## How current it is

`GET` lists each tenant by name with its status:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
GET /v1/namespaces/acme%2Fprod%2F*/tenant_summary
{
  "template": "acme/prod/*",
  "groups": ["by_plan", "abuse_by_day"],
  "warnings": [],
  "last_run_at": "2026-09-29T14:02:11.004Z",
  "tenants": [
    {"namespace": "acme/prod/tenant_123", "status": "published", "published_at": "2026-09-29T14:00:03.120Z", "source_as_of": 20488},
    {"namespace": "acme/prod/tenant_124", "status": "unchanged", "published_at": "2026-09-28T09:00:01.842Z", "source_as_of": 18230},
    {"namespace": "acme/prod/tenant_125", "status": "skipped", "reason": "no_groups", "published_at": null, "source_as_of": null},
    {"namespace": "acme/prod/tenant_126", "status": "pending", "published_at": null, "source_as_of": null}
  ],
  "next_cursor": null
}
```

| Status | Meaning |
| - | - |
| `published` | the last run wrote its document |
| `unchanged` | its labels had not moved, so nothing was written |
| `skipped` | with `reason`: `deleting` (its namespace is being deleted), `new_life` (its namespace was re-created while the run read it), or `no_groups` (none of the groups has merged there yet) |
| `pending` | no run has reached it yet |

A group is exact as of its tenant's last storage merge, at most about an hour behind its writes, and the run reads it once an hour and pages through your tenants. So a tenant document lags its tenant by up to a run plus the paging, not one hour: read `published_at` and `source_as_of` rather than assuming.

## Changing it

* **Groups and bands** change with `PATCH`, each replacing the setting whole. The next run rewrites every tenant document whose labels move, which re-judges each of them, so a `PATCH` without `"confirm": true` changes nothing and returns what that costs: one line per judgment on the template's namespace that reads tenant documents. Send it again with `"confirm": true` and it applies from the next run, unattended.
* **Delete** with `DELETE`. Runs stop; the tenant documents stay, as ordinary documents.

## Tenants that come and go

* A tenant created under the template gets its document at the first run after its groups first merge.
* A tenant deleted and created again under the same name has its document deleted and written anew, so the history of anything judging it splits at the new incarnation, as it does for the tenant itself.
* A tenant document is deleted only once a strong read says the tenant's namespace is deleted, never because a listing missed it.

## Billing and failures

Tenant documents are written like any write to the template's namespace: they bill your organization as writes and stored bytes, and judgments over them bill as any judgment does. An idle tenant writes nothing.

If the template's namespace refuses the writes, for example because it is over its budget with `on_exceeded: reject`, the tenant summary shows the warning `tenant_summary_failed` and the [events feed](/guides/events-feed) carries one `namespace.tenant_summary_failed` for the run, with the reason. Nothing is lost: the next run that can write catches every tenant up and clears the warning.


## Related topics

- [Relations](/concepts/relations.md)
- [Multi-tenant platforms](/guides/multi-tenant-platforms.md)
- [Templates](/guides/templates.md)
- [Get the tenant summary and each tenant's status](/api-reference/groups/get-the-tenant-summary-and-each-tenants-status.md)
- [Publish a tenant document per namespace under a template](/api-reference/groups/publish-a-tenant-document-per-namespace-under-a-template.md)
