[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"docs-entities\u002Fstatuses\u002Faggregate":3,"docs-tabs-entities\u002Fstatuses\u002Faggregate":6},{"content":4,"lastmod":5},"## Aggregate dictionary records\n\n`POST \u002Fv1\u002Fstatuses\u002Faggregate`\n\nCount (`count`) and numeric aggregations `sum`, `avg`, `min`, `max` over CRM dictionary records with a filter. The primary scenario is `count`: how many records are in a dictionary or match a filter.\n\n## Standard fields\n\nNumeric functions apply to numeric fields of a record:\n\n| Field | Purpose |\n|------|------------|\n| `sort` | Sort order — `min` \u002F `max` \u002F `avg` give the range of weights |\n| `id` | Record identifier |\n| `categoryId` | Funnel ID for dictionaries of the form `DEAL_STAGE_N` |\n\nThe fields `entityId`, `statusId`, `name`, `semantics` are categorical. Only `count` with a filter is available for them. Grouping via `groupBy` is not supported for dictionaries. CRM dictionaries have no user fields.\n\n## Request fields (body)\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `aggregate` | array | no | Array of aggregations of the form `{ \"field\": \"sort\", \"function\": \"min\" }`. Functions: `sum`, `avg`, `min`, `max` — only over the fields `sort`, `id`, `categoryId`. Without this parameter, the `count` of records matching the filter is returned |\n| `filter` | object | no | Filtering by record fields — `id`, `entityId`, `statusId`, `name`, `sort`, `semantics`, `categoryId`. Only exact equality on a single value. A list of values (`$in` or an array) is not supported on any field — such a filter is rejected with `400 UNSUPPORTED_FILTER`. Count each value with a separate call, or combine the calls in [`POST \u002Fv1\u002Fbatch`](\u002Fdocs\u002Fbatch).\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering). Example: `{ \"entityId\": \"DEAL_STAGE\" }` |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fstatuses\u002Faggregate\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"entityId\": \"DEAL_STAGE\" }\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fstatuses\u002Faggregate\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"entityId\": \"DEAL_STAGE\" }\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fstatuses\u002Faggregate', {\n  method: 'POST',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    filter: { entityId: 'DEAL_STAGE' },\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log('Stages in the funnel:', data.count)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fstatuses\u002Faggregate', {\n  method: 'POST',\n  headers: {\n    'X-Api-Key': 'YOUR_APP_KEY',\n    'Authorization': 'Bearer USER_SESSION_TOKEN',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    filter: { entityId: 'DEAL_STAGE' },\n  }),\n})\n\nconst { success, data } = await res.json()\n```\n\n## Other scenarios\n\nTotal number of records across all dictionaries — `count` without a filter:\n\n```json\n{}\n```\n\nRange and average sort value of funnel stages — numeric functions over the `sort` field:\n\n```json\n{\n  \"aggregate\": [\n    { \"field\": \"sort\", \"function\": \"min\" },\n    { \"field\": \"sort\", \"function\": \"max\" },\n    { \"field\": \"sort\", \"function\": \"avg\" }\n  ],\n  \"filter\": { \"entityId\": \"DEAL_STAGE\" }\n}\n```\n\nThe response to such a request contains a populated `data.aggregates`:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"count\": 8,\n    \"aggregates\": {\n      \"sort\": { \"min\": 10, \"max\": 80, \"avg\": 45 }\n    },\n    \"meta\": { \"totalRecords\": 8, \"recordsProcessed\": 8, \"truncated\": false }\n  }\n}\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data.count` | number | Number of records matching the filter |\n| `data.aggregates` | object | Results of numeric functions over fields. For `count` without functions — an empty object `{}` |\n| `data.meta.totalRecords` | number | Total number of records matching the filter |\n| `data.meta.recordsProcessed` | number | How many records were loaded for the calculation. For `count` — `0`, for numeric functions — the number of records processed |\n| `data.meta.truncated` | boolean | `true` when the numbers were computed over only some of the records that match the filter: fewer records were read than `data.count` promised — including when more than 5000 matched the filter — or the slice was cut short by a sub-page error. The size of the gap is reported in `data.meta.recordsShortfall`, the interrupted slice in `data.meta.pageErrorSample`. Always present, and `false` on a complete response. If the request has neither `groupBy` nor a numeric function, no records are fetched and the flag is always `false` |\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"count\": 8,\n    \"aggregates\": {},\n    \"meta\": {\n      \"totalRecords\": 8,\n      \"recordsProcessed\": 0,\n      \"truncated\": false\n    }\n  }\n}\n```\n\nWith numeric functions, the `data.aggregates` field is populated — see “Other scenarios”.\n\n## Error response example\n\n400 — numeric function over a nonexistent field:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"INVALID_PARAMS\",\n    \"message\": \"Field 'nope' not found. Available numeric fields: id, sort, categoryId.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 400 | `INVALID_PARAMS` | Unknown function — the message lists the allowed ones: `count`, `sum`, `avg`, `min`, `max` |\n| 400 | `INVALID_PARAMS` | Nonexistent field in `aggregate` — the message lists the numeric fields: `id`, `sort`, `categoryId` |\n| 400 | `INVALID_PARAMS` | Non-numeric field in `sum` \u002F `avg` \u002F `min` \u002F `max` — the message names the field type |\n| 400 | `INVALID_PARAMS` | `groupBy` passed — grouping is not available for dictionaries |\n| 400 | `UNSUPPORTED_FILTER` | A list of values — `$in` or an array — was passed in `filter`. The message names the field and lists the filterable ones: `id`, `entityId`, `statusId`, `name`, `sort`, `semantics`, `categoryId` |\n| 403 | `SCOPE_DENIED` | The API key does not have the `crm` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |\n| 429 | `RATE_LIMITED` | Rate limit exceeded: 300 requests per minute per portal. All API keys of the portal share one limit. The current limit is returned in the `x-ratelimit-limit` header. Retry after the delay in the `Retry-After` header |\n\nFor the full list of errors, see [Errors](\u002Fdocs\u002Ferrors).\n\n## Known specifics\n\n**`count` versus numeric functions.** `count` is computed with a single call to Bitrix24 regardless of volume and does not download records — that is why `data.meta.recordsProcessed` equals `0`. The functions `sum` \u002F `avg` \u002F `min` \u002F `max` load records page by page and compute on the Vibecode side. If more than 5000 records match the filter, `data.meta.truncated: true` is returned and the result is calculated over the first 5000. The ceiling is not the only reason for this marker: it also appears when fewer records were read than `data.count` promised, and the size of the gap is reported in `data.meta.recordsShortfall`.\n\n**Grouping is not available.** Dictionary fields are not part of the groupable set, so `groupBy` returns `400 INVALID_PARAMS`. To count records by dictionary type, call `count` with the filter `filter[entityId]` separately for each type.\n\n**The truncation marker travels with the number itself.** When a response arrives with `meta.truncated: true`, the marker `truncated: true` sits inside every field object in `data.aggregates` and on every element of `data.groups`, and `data.meta.warnings` gains a warning with the code `AGGREGATE_TRUNCATED`. A client that reads only the number itself therefore sees that it was computed over only some of the records. None of these markers appear on a complete response. Full details — [Aggregation POST — the 5000-record ceiling](\u002Fdocs\u002Fentity-api#aggregation-post-the-5000-record-ceiling).\n\n## See also\n\n- [List of dictionaries](.\u002Flist.md)\n- [Search dictionary records](.\u002Fsearch.md)\n- [Dictionary fields](.\u002Ffields.md)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","2026-10-23",{},1794739424914]