
## Агрегация реквизитов

`POST /v1/requisites/aggregate`

Подсчёт количества реквизитов с фильтрацией и группировкой.

**Стандартные поля для `groupBy`:**

- `rqInn`, `rqKpp`, `rqOgrn`, `rqOgrnip`, `rqOkpo` — налоговые/регистрационные идентификаторы (ИНН, КПП, ОГРН, ОГРНИП, ОКПО)
- `rqVatId` — VAT/налоговый номер (для не-РФ стран)
- `rqResidenceCountry` — страна резидентства
- `rqCompanyName` — название компании
- `entityTypeId` — тип владельца (1 — лид, 3 — контакт, 4 — компания)
- `presetId` — шаблон реквизита
- `active` — признак активности

Все поля в `aggregatable` — идентификаторы и категориальные коды, поэтому по ним работает `groupBy`. Группировка по `rqInn` (или другому идентификатору) — самый быстрый способ найти дубли реквизитов одним вызовом, без выгрузки всех записей. Числовые функции (`sum`/`avg`/`min`/`max`) по этим полям недоступны (это строки) — используйте для них пользовательские UF-поля числового типа.

**Контракт `count`.** Функция `count` принимает ТОЛЬКО `field: "*"` — `{ "field": "*", "function": "count" }`. Передача `field: "id"` (или любого другого имени) вернёт `400 INVALID_PARAMS` с сообщением `count aggregate requires field "*"`. Это намеренный контракт: `count` считает строки, а не значения конкретного поля.

**Пользовательские поля (UF):** UF-поля типов `integer`, `double`, `money` — для числовых функций; UF любого типа — для `groupBy`. На практике у реквизита обычно UF-поля строкового типа (ИНН, номер телефона, адрес) — их можно использовать только в `groupBy`. Полный список UF-полей конкретного портала приходит в тексте ошибки `INVALID_PARAMS`, если передать несуществующее имя.

## Поля запроса (body)

| Параметр | Тип | Обяз. | Описание |
|----------|-----|:-----:|---------|
| `aggregate` | array | нет | Массив агрегаций. Каждый элемент: `{ "field": "*", "function": "count" }`. Без параметра — только count |
| `filter` | object | нет | Фильтрация по полям `GET /v1/requisites/fields`.<br>[Синтаксис фильтрации](/docs/filtering) |
| `groupBy` | string \| string[] | нет | Поле или массив полей для группировки (максимум 5). Принимает UF-поля любого типа |

## Примеры

### curl — личный ключ

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/requisites/aggregate" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "active": true },
    "groupBy": "entityTypeId"
  }'
```

### curl — OAuth-приложение

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/requisites/aggregate" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": { "active": true },
    "groupBy": "entityTypeId"
  }'
```

### JavaScript — личный ключ

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/requisites/aggregate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: { active: true },
    groupBy: 'entityTypeId',
  }),
})

const { success, data } = await res.json()
console.log('Всего активных реквизитов:', data.count)
console.log('По типам владельцев:', data.groups)
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/requisites/aggregate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: { active: true },
    groupBy: 'entityTypeId',
  }),
})

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

> Для группировки по нескольким полям передайте массив: `"groupBy": ["entityTypeId", "presetId"]` (максимум 5).

## Другие сценарии

Общее количество реквизитов в портале — самый быстрый запрос, без выгрузки записей:

```json
{}
```

Поиск дублей по ИНН одним вызовом — группы с `count > 1` содержат повторяющиеся ИНН:

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

Группировка по UF-полю (любой тип — например, пользовательский классификатор):

```json
{ "groupBy": "UF_CRM_CLASSIFIER" }
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | Всегда `true` при успехе |
| `data.count` | number | Количество записей, соответствующих фильтру |
| `data.aggregates` | object | Результаты агрегаций (для реквизитов обычно пустой) |
| `data.groups` | array | Группы (только при `groupBy`). Каждый элемент: поля группировки + `count` |
| `data.meta.totalRecords` | number | Общее количество записей |
| `data.meta.recordsProcessed` | number | Количество обработанных записей |
| `data.meta.truncated` | boolean | Был ли результат ограничен. `true` при более 5000 записей |

## Пример ответа

Ответ на основной запрос (`groupBy: "entityTypeId"`):

```json
{
  "success": true,
  "data": {
    "count": 164,
    "aggregates": {},
    "groups": [
      { "entityTypeId": 4, "count": 120 },
      { "entityTypeId": 3, "count": 40 },
      { "entityTypeId": 1, "count": 4 }
    ],
    "meta": {
      "totalRecords": 164,
      "recordsProcessed": 164,
      "truncated": false
    }
  }
}
```

Без `groupBy` поле `data.groups` в ответе отсутствует.

## Пример ответа при ошибке

403 — нет скоупа:

```json
{
  "success": false,
  "error": {
    "code": "SCOPE_DENIED",
    "message": "Requires 'crm' scope"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 400 | `INVALID_PARAMS` | Невалидное имя функции агрегации или несуществующее поле |
| 403 | `SCOPE_DENIED` | API-ключ не имеет скоупа `crm` |
| 401 | `TOKEN_MISSING` | API-ключ не имеет настроенных токенов |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Money-поля.** UF-поля типа `money` хранятся в формате `"сумма|валюта"` (`"1500|RUB"`) — агрегат извлекает числовую часть автоматически, складывать можно без парсинга.

**Без массива `aggregate` — только count.** Если не передать `aggregate`, метод вернёт `count` записей с учётом фильтра.

**Фильтрация по UF работает.** В `filter` можно передавать любые поля — стандартные и пользовательские, любого типа. Например, `{ "filter": { "UF_CRM_1234": "value" } }` вернёт количество реквизитов с этим значением UF.

**Ограничение 5000 записей.** Если под фильтр попадает больше 5000 записей, результат будет помечен `meta.truncated: true`. Для точного подсчёта больших выборок используйте `meta.total` в ответе `GET /v1/requisites` с `limit=1` — там хранится реальное количество.

## Смотрите также

- [Список реквизитов](/docs/entities/requisites/list) — `meta.total` даёт точное число записей
- [Поиск реквизитов](/docs/entities/requisites/search) — POST-запрос с фильтрами
- [Синтаксис фильтрации](/docs/filtering)
- [Лимиты и оптимизация](/docs/optimization) — rate limits
