---
title: "Count records per group"
method: GET
path: "/api/v1/records/group-by"
tags: ["Records"]
---

# Count records per group

`GET /api/v1/records/group-by`

## Request

- Base URL: `https://api-docs.busabase.com`
- URL: `https://api-docs.busabase.com/api/v1/records/group-by`
- Auth: none declared

One SQL GROUP BY returning every bucket's exact count — the split a board column header or a summary tile needs, without reading the records themselves. `fieldSlug` must name a `select` or `checkbox` field: their stored value IS the grouping key, so the buckets are exactly the ones a client would build. Grouping by a text, number or date field is rejected rather than approximated (text keys are truncated at the projection limit; date keys would bucket by the server's timezone, not the viewer's). `viewId` and `filters` narrow the set first, with the same exactness rules as `records.count`: provably-exact conditions stay a cheap SQL aggregate, anything else falls back to evaluating every matching row server-side — exact, but not free on a large Base. Groups come back keyed by raw choice id (or `"true"`/`"false"`), with `null` for records that have no value; labels are the client's to render.

## Query parameters

- `baseId` string, required: Required: a field slug is only unambiguous within one Base.
- `fieldSlug` string: The field to group by. OMIT it to aggregate the whole filtered set as a single bucket, which is what a summary tile wants. Under the default `grid` bucketing only `select` and `checkbox` can be grouped; `sql` bucketing also allows number and date fields.
- `bucketing` 'grid' | 'sql': How records are bucketed, and the two modes disagree on real data. `grid` (default) buckets the way the grid renders: an unset checkbox counts as `false` and an empty string falls in the null bucket — right for a Kanban column header. `sql` buckets the way GROUP BY does: a missing value gets its OWN bucket and nothing is folded — right for anything reproducing SQL. `sql` also returns keys in their own type (a number for a number field) rather than as strings.
- `aggregates` object[]: Numeric aggregates evaluated per group, keyed in the response as `"<fn>:<fieldSlug>"`. Only number-shaped fields can be aggregated; anything else is a 400. `sum`/`avg`/`min`/`max` of a group holding no values are NULL rather than 0, and `count` over a FIELD counts present values — which is not the same as the group's own `count`, which counts records.
  - `fn` 'sum' | 'avg' | 'min' | 'max' | 'count', required
  - `fieldSlug` string, required
- `viewId` string: Group only what this saved View would display. Its filters apply; its sort is ignored.
- `filters` object[]: Ad-hoc conditions, ANDed with the View's own when both are given. The grouping is exact either way, but a condition whose exactness cannot be proven makes the server read every candidate row instead of running one GROUP BY.
  - `fieldSlug` string, required
  - `fieldType` string
  - `operator` 'contains' | 'equals' | 'not_empty' | 'is_empty' | 'is_true' | 'is_false', required
  - `value` unknown
- `valueFilters` union[]: EXACT value comparisons, same shape as `records.list`'s. Always exact, so a grouping scoped only by these stays a single SQL GROUP BY.
  - union
    - object
      - `fieldSlug` string, required
      - `operator` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte', required
      - `value` union, required
        - number
        - string
        - boolean
    - object
      - `any` object[], required
        - `fieldSlug` string, required
        - `operator` 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte', required
        - `value` union, required
          - number
          - string
          - boolean

## Response `200`

Every group's exact count, plus the total across all groups. Groups with zero records are omitted — a Base's full choice list lives in its field definition, so the client already knows which buckets to render empty.

- object
  - `groups` object[], required
    - `value` union, required: The bucket key. Under `grid` bucketing always a string or null (a select's choice, or `"true"`/`"false"` for a checkbox, with null meaning "no value"). Under `sql` bucketing it is the stored value in its own type, and null is the bucket of records that have no value for the field.
      - string
      - number
      - boolean
    - `count` integer, required: Records in this bucket.
    - `aggregates` object: Present only when `aggregates` was requested, keyed `"<fn>:<fieldSlug>"`. A null value means the bucket held no records with that field set — not zero.
  - `total` integer, required

## Other responses

- `400`: 400
- `404`: 404

---

[API](https://skmtc.dev/busabase/apis/busabase-api.md) · [All operations](https://skmtc.dev/busabase/apis/busabase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/busabase/apis/busabase-api/revisions/bc6a7d5ff4c1?raw)
