Для AI-агентов: markdown этой страницы — /docs-content/entities/deals/aggregate.md индекс документации — /llms.txt

Агрегация сделок

POST /v1/deals/aggregate

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

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

  • amount — сумма сделки (числовое агрегирование имеет смысл)
  • stageId — стадия (для groupBy)
  • categoryId — воронка (для groupBy)
  • assignedById — ответственный (для groupBy)
  • sourceId — источник (для groupBy)

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

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

Параметр Тип Обяз. Описание
aggregate array нет Массив агрегаций. Каждый элемент: { "field": "amount", "function": "sum" }. Функции: count, sum, avg, min, max. Для count поле — "*". Без массива — только count
filter object нет Фильтрация по полям GET /v1/deals/fields. Синтаксис фильтрации
groupBy string | string[] нет Поле или массив полей для группировки (максимум 5). Допустимые значения — из списка выше

Примеры

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

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/deals/aggregate" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "aggregate": [
      { "field": "amount", "function": "sum" },
      { "field": "amount", "function": "avg" }
    ],
    "filter": { "categoryId": 0 },
    "groupBy": "stageId"
  }'

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

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/deals/aggregate" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "aggregate": [
      { "field": "amount", "function": "sum" },
      { "field": "amount", "function": "avg" }
    ],
    "filter": { "categoryId": 0 },
    "groupBy": "stageId"
  }'

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

javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/deals/aggregate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    aggregate: [
      { field: 'amount', function: 'sum' },
      { field: 'amount', function: 'avg' },
    ],
    filter: { categoryId: 0 },
    groupBy: 'stageId',
  }),
})

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/deals/aggregate', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    aggregate: [
      { field: 'amount', function: 'sum' },
      { field: 'amount', function: 'avg' },
    ],
    filter: { categoryId: 0 },
    groupBy: 'stageId',
  }),
})

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

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

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

Подсчёт записей — count с полем "*", самый быстрый запрос без выгрузки записей. Без массива aggregate результат тот же:

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

Работа с пользовательскими полями (UF) — sum по UF + группировка по другому UF. Имя поля берётся из схемы GET /v1/deals/fields, на другое написание приходит 400 INVALID_PARAMS со списком доступных полей:

JSON
{
  "aggregate": [{ "field": "ufCrmBudget", "function": "sum" }],
  "groupBy": "ufCrmPriority"
}

Сводка по воронке — один запрос с группировкой по стадии и суммой даёт разбивку, из которой на стороне приложения считается конверсия в выигранные:

JSON
{
  "aggregate": [{ "field": "amount", "function": "sum" }],
  "filter": { "categoryId": 0 },
  "groupBy": "stageId"
}

В ответе data.count — всего сделок в воронке, data.groups — разбивка по стадиям с суммой и количеством. Идентификаторы стадий зависят от воронки — получить их можно из Поля. Конверсия считается из групп без отдельного запроса:

javascript
const total = data.count
const won = data.groups.find(g => g.stageId === 'WON')?.count ?? 0
const conversion = total ? Math.round((won / total) * 100) : 0
console.log(`Сделок в воронке: ${total}, выиграно: ${won}, конверсия: ${conversion}%`)

Поля ответа

Поле Тип Описание
success boolean Всегда true при успехе
data.count number Общее количество записей под фильтр
data.aggregates object Результаты агрегаций: { "amount": { "sum": 100000, "avg": 2439 } }
data.groups array Группы (только при groupBy). Каждый элемент: поля группировки + count + aggregates
data.meta.totalRecords number Общее количество записей под фильтр
data.meta.recordsProcessed number Сколько записей обработано для числовых агрегаций (максимум 5000)
data.meta.truncated boolean true, если числа посчитаны не по всем записям под фильтр: прочитано меньше записей, чем обещал ответ — в том числе когда под фильтр попало больше 5000, — либо срез оборвался ошибкой подстраницы. Обещанное это data.count, а в режиме счётчиков по стадиям — большее из data.count и суммы счётчиков стадий. Размер нехватки приходит в data.meta.recordsShortfall, оборванный срез — в data.meta.pageErrorSample. Присутствует всегда, на полном ответе равен false. Если в запросе нет ни groupBy, ни числовых функций, записи не выгружаются и флаг всегда false

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

Ответ на основной запрос (агрегации + groupBy: "stageId"):

JSON
{
  "success": true,
  "data": {
    "count": 41,
    "aggregates": {
      "amount": { "sum": 100000, "avg": 2439 }
    },
    "groups": [
      {
        "stageId": "NEW",
        "count": 25,
        "aggregates": { "amount": { "sum": 60000 } }
      },
      {
        "stageId": "WON",
        "count": 16,
        "aggregates": { "amount": { "sum": 40000 } }
      }
    ],
    "meta": {
      "totalRecords": 41,
      "recordsProcessed": 41,
      "truncated": false
    }
  }
}

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

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

400 — неверное имя функции, несуществующее поле или groupBy по неаггрегируемому полю:

JSON
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Field 'foo' not found. Available numeric fields: id, amount, categoryId, companyId, mycompanyId, contactId, assignedById, createdBy, probability, webformId, updatedBy, movedBy, repeatSaleSegmentId, leadId, quoteId, lastActivityBy, taxValue, receivedAmount, lostAmount."
  }
}

Ошибки

HTTP Код Описание
400 INVALID_PARAMS Некорректное имя функции, несуществующее поле, нечисловое поле в sum/avg/min/max, groupBy по неаггрегируемому полю или больше 5 полей в groupBy
401 TOKEN_MISSING API-ключ не имеет настроенных токенов
403 SCOPE_DENIED API-ключ не имеет скоупа crm
429 RATE_LIMITED Превышен лимит запросов: 300 в минуту на портал, все API-ключи портала делят один лимит. Точное значение — в заголовке x-ratelimit-limit (потолок делится на реплики). Повторите после срока из заголовка Retry-After

Полный список общих ошибок API — Ошибки.

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

count и числовые функции — в чём разница. count без groupBy считается одним вызовом в Битрикс24 на любом объёме данных. Функции sum/avg/min/max подгружают записи постранично (максимум 5000) и считают на стороне Вайбкод — если под фильтр попадает больше 5000 записей, meta.truncated будет true, агрегация выполнится по первым 5000. Для точных счётчиков на больших выборках используйте count или сужайте фильтр. Потолок общий для всех сущностей — Агрегация POST — потолок 5000 записей. Потолок не единственная причина такой пометки: она появляется и когда прочитано меньше записей, чем обещал ответ, — а в режиме счётчиков по стадиям обещанное это большее из data.count и суммы счётчиков. Размер нехватки лежит в data.meta.recordsShortfall.

Крупная воронка: два режима, включаются администратором платформы по аккаунтам.

Оба выключены по умолчанию. Пока они выключены, поведение ровно такое, как описано выше.

  • Отказ вместо усечения. Запрос, которому для ответа нужны строки (числовые функции и/или groupBy), при total > 5000 отвечает 422 AGGREGATION_LIMIT_EXCEEDED и не выгружает ни одной записи. Раньше он всё равно выгружал первые 5000 — на крупной воронке эта выгрузка не успевала и запрос обрывался по таймауту. В тексте ошибки перечислено, что делать дальше.
  • Счётчики по стадиям без чтения строк. groupBy: ["stageId"] или ["stageSemanticId"] со скалярным categoryId в фильтре отвечает на воронке любого размера: количество по каждой стадии берётся отдельным дешёвым подсчётом на стороне Битрикс24. Признак такого ответа — meta.aggregatePath: "fanout" при meta.recordsProcessed: 0: строки не читались вовсе, поэтому счётчики точны. Числовые функции по стадиям остаются доступны, пока суммарный размер групп укладывается в 5000 — но им строки уже нужны, и такой ответ может прийти усечённым: тогда meta.truncated станет true, а числа получат пометку. Сами счётчики стадий при этом остаются точными — они берутся пробой, а не из прочитанных строк. Потолок не единственная причина такой пометки: она появляется и когда прочитано меньше записей, чем обещал ответ, — а в режиме счётчиков по стадиям обещанное это большее из data.count и суммы счётчиков. Размер нехватки лежит в data.meta.recordsShortfall.

meta.stageCountDelta приходит только в ответе со счётчиками по стадиям: разница между общим количеством и суммой по стадиям. Ноль означает, что разбиение полное. Ненулевое значение означает, что часть записей не попала в разбиение — записи изменились между подсчётами либо часть из них лежит в стадии, которой больше нет в справочнике стадий воронки. Счётчики стадий и count от этого точны: count всегда равен общему количеству, а не сумме групп. А вот числовые агрегаты в таком ответе посчитаны по меньшему набору строк, поэтому он приходит с meta.truncated: true и пометками у чисел.

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

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

Устаревший GET aggregate

Устаревший GET /v1/{entity}/aggregate?op=sum&field=amount сохранён для совместимости; для новых клиентов используйте POST. В GET поле data.meta всегда содержит recordsProcessed и truncated. У частичного ответа также появляются data.truncated, groups[].truncated и total.truncated, если соответствующие объекты есть, а в meta.warnings — AGGREGATE_TRUNCATED. Известный totalRecords и положительный recordsShortfall передаются отдельно; pageErrorSample содержит только санитизированные code/message ошибки страницы. Неизвестные totalRecords и recordsShortfall отсутствуют: явный признак неполноты пагинатора сохраняется без измеримого недобора и ошибки страницы, в том числе если заявленный total совпал с числом полученных строк. GET count считает полученные строки и может быть частичным; точный POST count не является свойством GET. Прежний предел GET в 10000 записей сохраняется: превышение возвращает 422 AGGREGATION_LIMIT_EXCEEDED.

Пример data для двух полученных строк с неизвестным общим размером:

JSON
{
  "op": "sum",
  "field": "amount",
  "value": 100,
  "count": 2,
  "truncated": true,
  "meta": {
    "recordsProcessed": 2,
    "truncated": true,
    "warnings": [
      {
        "code": "AGGREGATE_TRUNCATED",
        "message": "This aggregate was computed over only the records in data.meta.recordsProcessed. Values and group counts describe a partial sample, not the full matching collection. Narrow the filter before treating the result as complete."
      }
    ]
  }
}

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