
## Aggregate document templates

`POST /v1/doc-templates/aggregate`

Count and numeric aggregations (sum, average, minimum, maximum) over document templates with filtering and grouping.

**Standard fields:**

- `sort` — sort index, the only numeric field where `sum`/`avg`/`min`/`max` are meaningful
- `numeratorId` — numerator, a number, works in numeric functions and in `groupBy`
- `fileId` — template file on Drive, a number, works in numeric functions and in `groupBy`
- `region` — template region, a string, for `groupBy` only
- `moduleId` — owner module, a string, for `groupBy` only
- `active` — active flag, a string, for `groupBy` only

The `sum`/`avg`/`min`/`max` functions accept only numeric fields — `sort`, `numeratorId`, `fileId`. A string field in a numeric function returns `400 INVALID_PARAMS`. `groupBy` accepts any field from the list above.

## Request body

| Parameter | Type | Required | Description |
|----------|-----|:-----:|---------|
| `aggregate` | array | no | Array of aggregations. Each item: `{ "field": "sort", "function": "sum" }`. Functions: `count`, `sum`, `avg`, `min`, `max`. For `count` the field is `"*"`. Without the array — `count` only |
| `filter` | object | no | Filtering by the fields from `GET /v1/doc-templates/fields`. [Filtering syntax](/docs/filtering) |
| `groupBy` | string \| string[] | no | A field or an array of fields to group by (up to 5). Allowed values are from the list above |

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/doc-templates/aggregate" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "aggregate": [
      { "field": "sort", "function": "sum" },
      { "field": "sort", "function": "avg" }
    ],
    "groupBy": "region"
  }'
```

### curl — OAuth app

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/doc-templates/aggregate" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "aggregate": [
      { "field": "sort", "function": "sum" },
      { "field": "sort", "function": "avg" }
    ],
    "groupBy": "region"
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/doc-templates/aggregate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    aggregate: [
      { field: 'sort', function: 'sum' },
      { field: 'sort', function: 'avg' },
    ],
    groupBy: 'region',
  }),
})

const { success, data } = await res.json()
console.log('Total templates:', data.count)
console.log('By region:', data.groups)
```

### JavaScript — OAuth app

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/doc-templates/aggregate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    aggregate: [
      { field: 'sort', function: 'sum' },
      { field: 'sort', function: 'avg' },
    ],
    groupBy: 'region',
  }),
})

const { success, data } = await res.json()
```

> To group by several fields, pass an array: `"groupBy": ["region", "active"]` (up to 5).

## Other scenarios

Record count — `count` with the field `"*"`, the fastest request without loading templates. Without the `aggregate` array the result is the same:

```json
{ "aggregate": [{ "field": "*", "function": "count" }] }
```

Breakdown by two fields at once — how many templates fall into each combination of region and active flag:

```json
{ "groupBy": ["region", "active"] }
```

Number of templates in a single region — `count` with a filter, records are not loaded:

```json
{ "filter": { "region": "ru" } }
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | Always `true` on success |
| `data.count` | number | Total number of templates matching the filter |
| `data.aggregates` | object | Numeric aggregation results: `{ "sort": { "sum": 1650, "avg": 275 } }`. An empty object when the `aggregate` array is not passed |
| `data.groups` | array | Groups, present only with `groupBy`. Each item: grouping fields + `count` + `aggregates` |
| `data.meta.totalRecords` | number | Total number of records matching the filter |
| `data.meta.recordsProcessed` | number | How many records were processed for numeric aggregations. Equals `0` for a request without numeric functions — records are not loaded |
| `data.meta.truncated` | boolean | `true` if more than 5000 records matched the filter |
| `data.meta.groupTotal` | number | Number of groups, present only with `groupBy` |
| `data.meta.groupsTruncated` | boolean | `true` if there were more groups than the output limit. Present only with `groupBy` |

## Response example

Response to the main request (`sum` and `avg` over `sort` with `groupBy: "region"`):

```json
{
  "success": true,
  "data": {
    "count": 6,
    "aggregates": {
      "sort": { "sum": 1650, "avg": 275 }
    },
    "groups": [
      {
        "region": "ru",
        "count": 3,
        "aggregates": { "sort": { "sum": 600, "avg": 200 } }
      },
      {
        "region": "by",
        "count": 2,
        "aggregates": { "sort": { "sum": 900, "avg": 450 } }
      },
      {
        "region": "kz",
        "count": 1,
        "aggregates": { "sort": { "sum": 150, "avg": 150 } }
      }
    ],
    "meta": {
      "totalRecords": 6,
      "recordsProcessed": 6,
      "truncated": false,
      "groupTotal": 3,
      "groupsTruncated": false
    }
  }
}
```

Without `groupBy` the fields `data.groups`, `data.meta.groupTotal` and `data.meta.groupsTruncated` are absent from the response.

## Error response example

400 — a non-existent field. The message lists the available fields:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Field 'bogus' not found. Available numeric fields: sort, numeratorId, fileId, region, moduleId, active."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `INVALID_PARAMS` | Invalid function name or a non-existent field — the message lists the available fields |
| 400 | `INVALID_PARAMS` | A string field (`region`, `moduleId`, `active`) in `sum`/`avg`/`min`/`max` — the message names the field type |
| 400 | `INVALID_PARAMS` | `groupBy` by a field outside the list, a reserved word (`count`, `aggregates`, `meta`, `groups`), or more than 5 fields in `groupBy` |
| 403 | `SCOPE_DENIED` | The API key lacks the `documentgenerator` scope |
| 401 | `TOKEN_MISSING` | The API key has no configured tokens |

For the full list of common API errors, see [Errors](/docs/errors).

## Notes

**`count` vs numeric functions.** `count` is computed with a single call to Bitrix24 for any data volume, without loading records, and `meta.recordsProcessed` equals `0`. The `sum`/`avg`/`min`/`max` functions load records page by page (up to 5000) and compute on the Vibecode side — if more than 5000 records match the filter, `meta.truncated` becomes `true` and the aggregation runs over the first 5000. For exact counts on large selections, use `count` or narrow the filter.

**Numeric aggregation is over a single field.** Only `sort` yields a meaningful sum or average. The `numeratorId` and `fileId` fields are numeric too and are accepted in `sum`/`avg`/`min`/`max`, but they are identifiers — aggregate them for grouping, not for summation. The remaining fields are strings and work only in `groupBy`.

## See also

- [List templates](/docs/entities/doc-templates/list)
- [Search templates](/docs/entities/doc-templates/search)
- [Template fields](/docs/entities/doc-templates/fields)
- [Filtering syntax](/docs/filtering)
- [Limits and optimization](/docs/optimization)
