[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"docs-entities\u002Frequisites\u002Faggregate":3,"docs-tabs-entities\u002Frequisites\u002Faggregate":6},{"content":4,"lastmod":5},"\n## Aggregate requisites\n\n`POST \u002Fv1\u002Frequisites\u002Faggregate`\n\nCounts requisites with filtering and grouping.\n\n**Standard fields for `groupBy`:**\n\n- `rqInn`, `rqKpp`, `rqOgrn`, `rqOgrnip`, `rqOkpo` — tax\u002Fregistration identifiers\n- `rqVatId` — VAT \u002F tax number (used by international account presets)\n- `rqResidenceCountry` — country of residence\n- `rqCompanyName` — company name\n- `entityTypeId` — owner type (1 — lead, 3 — contact, 4 — company)\n- `presetId` — requisite preset\n- `active` — active flag\n\nAll fields in `aggregatable` are identifiers and categorical codes, so `groupBy` works on them. Grouping by `rqVatId` (or another identifier) is the fastest way to find duplicate requisites in a single call, without fetching every record. Numeric functions (`sum`\u002F`avg`\u002F`min`\u002F`max`) are not available on these fields (they are strings) — use custom UF fields of a numeric type for those.\n\n**The `count` contract.** The `count` function accepts ONLY `field: \"*\"` — `{ \"field\": \"*\", \"function\": \"count\" }`. Passing `field: \"id\"` (or any other name) returns `400 INVALID_PARAMS` with the message `count aggregate requires field \"*\"`. This is intentional: `count` counts rows, not the values of a specific field.\n\n**Custom fields (UF):** UF fields of type `integer`, `double`, `money` work with numeric functions; UF fields of any type work with `groupBy`. String-type UF fields of a requisite — tax ID, phone number, address — are available only in `groupBy`. The full list of UF fields for a specific Bitrix24 account is returned in the `INVALID_PARAMS` error message if you pass a nonexistent name.\n\n## Request fields (body)\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `aggregate` | array | no | Array of aggregations. Each element: `{ \"field\": \"*\", \"function\": \"count\" }`. Without the parameter — only `count` |\n| `filter` | object | no | Filtering by `GET \u002Fv1\u002Frequisites\u002Ffields` fields.\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering) |\n| `groupBy` | string \\| string[] | no | Field or array of fields to group by (max 5). Accepts UF fields of any type |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Frequisites\u002Faggregate\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"active\": true },\n    \"groupBy\": \"entityTypeId\"\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Frequisites\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\": { \"active\": true },\n    \"groupBy\": \"entityTypeId\"\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Frequisites\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: { active: true },\n    groupBy: 'entityTypeId',\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log('Total active requisites:', data.count)\nconsole.log('By owner type:', data.groups)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Frequisites\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: { active: true },\n    groupBy: 'entityTypeId',\n  }),\n})\n\nconst { success, data } = await res.json()\n```\n\n> To group by multiple fields, pass an array: `\"groupBy\": [\"entityTypeId\", \"presetId\"]` (max 5).\n\n## Other scenarios\n\nThe blocks below are request bodies.\n\nThe total number of requisites in the Bitrix24 account — the fastest request, without fetching records:\n\n```json\n{}\n```\n\nFind duplicates by VAT ID in a single call — groups with `count > 1` contain the duplicated VAT IDs:\n\n```json\n{ \"aggregate\": [{ \"field\": \"*\", \"function\": \"count\" }], \"groupBy\": \"rqVatId\" }\n```\n\nGrouping by a UF field (any type — for example, a custom classifier):\n\n```json\n{ \"groupBy\": \"UF_CRM_CLASSIFIER\" }\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 | Numeric aggregation results. Empty when the `aggregate` array is not passed or holds only `count` |\n| `data.groups` | array | Groups (only with `groupBy`). Each element: grouping fields + `count` |\n| `data.meta.totalRecords` | number | Total number of records |\n| `data.meta.recordsProcessed` | number | Number of processed records |\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\nResponse to the main request (`groupBy: \"entityTypeId\"`):\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"count\": 164,\n    \"aggregates\": {},\n    \"groups\": [\n      { \"entityTypeId\": 4, \"count\": 120 },\n      { \"entityTypeId\": 3, \"count\": 40 },\n      { \"entityTypeId\": 1, \"count\": 4 }\n    ],\n    \"meta\": {\n      \"totalRecords\": 164,\n      \"recordsProcessed\": 164,\n      \"truncated\": false\n    }\n  }\n}\n```\n\nWithout `groupBy`, the `data.groups` field is absent from the response.\n\n## Error response example\n\n403 — no scope:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"SCOPE_DENIED\",\n    \"message\": \"Requires 'crm' scope\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 400 | `INVALID_PARAMS` | Invalid aggregation function name or nonexistent field |\n| 403 | `SCOPE_DENIED` | API key does not have the `crm` scope |\n| 401 | `TOKEN_MISSING` | API key has no configured tokens |\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 common API errors, see [Errors](\u002Fdocs\u002Ferrors).\n\n## Known specifics\n\n**Money fields.** UF fields of type `money` are stored in the format `\"amount|currency\"` (`\"1500|USD\"`) — the aggregate extracts the numeric part automatically, so you can sum them without parsing.\n\n**Without the `aggregate` array — only `count`.** If you do not pass `aggregate`, the method returns the `count` of records matching the filter.\n\n**Filtering by UF works.** In `filter` you can pass any fields — standard and custom, of any type. For example, `{ \"filter\": { \"UF_CRM_1234\": \"value\" } }` returns the number of requisites with that UF value.\n\n**The 5000-record ceiling.** Numeric functions and grouping fetch records page by page, up to 5000. If more records match the filter, the response arrives with `meta.truncated: true`. Take the exact count from `data.count` — it comes from a separate count and is correct at any result-set size. 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**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. For full details, see [Aggregation POST — the 5000-record ceiling](\u002Fdocs\u002Fentity-api#aggregation-post-the-5000-record-ceiling).\n\n## See also\n\n- [List requisites](\u002Fdocs\u002Fentities\u002Frequisites\u002Flist)\n- [Search requisites](\u002Fdocs\u002Fentities\u002Frequisites\u002Fsearch)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","2026-10-23",{},1794739412987]