> ## 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.

# Report on groups of documents

> Declare a group-by once, such as frustrated conversations by plan this month, and read its counts, sums and averages per key value with no pipeline.

export const productName = "Vainona";

Every team asks for the same dashboard numbers: "frustrated conversations by plan, this month", "refund requests per merchant category", "average first-reply time per region". The usual answer is a warehouse job over an export. In {productName} it is a **group**: a group-by you declare once on a namespace. It keeps its aggregates per value of a key attribute, over your documents and over your judgments' answers, and you read the rows whenever you want them.

A group reports. It is not a query planner and not an input to a judgment: it keeps exactly the aggregates you declared, and nothing reads it but you. To give a judgment other documents' facts or answers, use a [relation](/concepts/relations).

## Frustrated conversations by plan, this month

Say each conversation carries its account's plan and the month it started, which you write anyway, and a `frustrated` judgment answers for each one. Write the key you want to report by as one attribute, here `attributes.plan_month`, such as `"pro/2026-09"`:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
POST /v1/namespaces/acme%2Fsupport/groups
{
  "name": "frustration_by_plan",
  "key": "attributes.plan_month",
  "match": {"attributes.kind": "conversation"},
  "aggregate": {
    "count": true,
    "count_where": {"answers.frustrated.p": {"gte": 0.7}},
    "avg": ["state.first_reply_minutes"]
  },
  "keep_keys": "90d"
}
```

The create returns `202` with the group `building` and its `group_build` job, which counts what the namespace already holds. It judges nothing. Once it is done the group reads `ready`, and every later change is counted as the namespace's storage merges it.

Read the rows with a get:

```json theme={"theme":{"light":"css-variables","dark":"css-variables"}}
GET /v1/namespaces/acme%2Fsupport/groups/frustration_by_plan
{
  "name": "frustration_by_plan",
  "status": "ready",
  "live_keys": 6,
  "as_of": 20488,
  "rows": [
    {"key": "free/2026-09", "values": {"count": 41208, "count_where": 2110, "avg(state.first_reply_minutes)": 312.5}},
    {"key": "pro/2026-09", "values": {"count": 17120, "count_where": 1433, "avg(state.first_reply_minutes)": 48.2}},
    {"key": "enterprise/2026-09", "values": {"count": 3874, "count_where": 402, "avg(state.first_reply_minutes)": 11.9}}
  ]
}
```

Rows come largest `count` first. A value is null when no document in the row has a number at that path. The dashboard's Groups tab on the namespace charts the largest key values and lists every row beside the chart.

## What a group keeps

| Aggregate | Counts |
| - | - |
| `count` | the live documents that match `match` |
| `count_where` | those whose answer from one judgment meets every condition: `answers.<judgment>.value` or `.thresholds.<name>` by equality (a list is "any of"), or `.p` or `.score` by one bound such as `{"gte": 0.7}` |
| `sum`, `avg`, `min`, `max` | a `state` or `attributes` path over the matching documents; values that are not numbers are skipped |

`count_where` reads a judgment that answers on its own: active, `on_change`, and neither reading other documents nor combining parts. A raw probability is never summed or averaged, only counted by a bound or a named threshold, so the numbers you read are the ones you would query on. A group keeps at most 8 paths across its aggregates and `count_where`.

## How current the rows are

The rows are exact as of the namespace's last storage merge, and `as_of` is that merge's position. A namespace with a group merges at least once an hour, so the rows are at most about an hour behind your writes, and busy namespaces merge far more often. A key value no merged document has yet reads nothing until the next merge. Answers count once they are merged too, under their document's attributes as merged: a conversation moved to another plan counts under its new key from the merge that moves it.

## Changing a group

* **Add aggregates** with `PATCH` and `{"aggregate": {...}}`: the group reads `building` while a new `group_build` job counts them, then `ready`. An aggregate never changes meaning and is never removed; a group has at most one `count_where`. To report something else, create another group.
* **Change `keep_keys`** with `PATCH`, at once.
* **Delete** with `DELETE`. It judges nothing and changes no answers.

## Limits

* **8 groups** per namespace, counting those it inherits from a [template](/guides/templates).
* **1,000 live key values** per group. A key value is live while it has a matching document created within `keep_keys` (90 days by default), so a month or day bucket ages out on its own. At create, a key attribute with more live values than that is refused with `invalid_request`: key the group on something coarser, such as a merchant category instead of a merchant. A group that grows past it later carries the warning `group_over_cap`, and its rows hold the 1,000 key values with the largest `count` until older ones age out.

See [limits](/limits) for every number.

## On a template

A group created on a template prefix, such as `acme/prod/*`, exists in every namespace under it, each counting only its own documents. Read each tenant's rows on its own path; the prefix path returns the definition with `rows: null`. A tenant cannot change or delete a group it inherits (`conflict`). A template's [tenant summary](/guides/tenant-summary) publishes these rows, banded, as a document per tenant.

## Accuracy

A group counts what your documents and answers say. It makes no claim about how accurate those answers are: `count_where` is as good as the judgment it counts, and [measuring and improving it](/guides/measure-improve-tune) is how you know.

## Cost

A group never calls an engine, so it adds no judgments to your bill. Its partial counts are stored beside your data, a small object per merged segment, and count toward the namespace's stored bytes.


## Related topics

- [Relations](/concepts/relations.md)
- [Create a group](/api-reference/groups/create-a-group.md)
- [List groups](/api-reference/groups/list-groups.md)
- [Delete a group](/api-reference/groups/delete-a-group.md)
- [Get a group and its rows](/api-reference/groups/get-a-group-and-its-rows.md)
