> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brussle.com/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.

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 Brussle 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 POST /v1/namespaces/acme%2Fsupport/groups theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "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, including documents written just before. It judges nothing. Once it is done the group reads `ready`, and every later change is counted, at most about an hour after it is written. Sending the same create again returns the group; a create whose name another group already has, with other settings or inherited from a template, is `conflict`.

Read the rows with a get:

```json GET /v1/namespaces/acme%2Fsupport/groups/frustration_by_plan theme={"theme":{"light":"css-variables","dark":"css-variables"}}
{
  "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",
  "status": "ready",
  "job_id": null,
  "live_keys": 3,
  "warnings": [],
  "created_at": "2026-09-01T10:02:44Z",
  "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 one position in the namespace's history, and `as_of` is that position. They catch up at least once an hour, so they are at most about an hour behind your writes, and on busy namespaces far less. A key value that no counted document has yet reads nothing until the rows next catch up. Answers count once the rows catch up with them too, under their document's attributes at that point: a conversation moved to another plan counts under its new key from the update 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 their bands, never their values, 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 counts are stored beside your data, a small amount of storage, 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)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.