[{"data":1,"prerenderedAt":15},["ShallowReactive",2],{"docs-entities\u002Fcatalog-services":3,"docs-tabs-entities\u002Fcatalog-services":6},{"content":4,"lastmod":5},"# Catalog services\n\nServices of the product catalog: list, get, create, update, and delete. A service is a catalog record of type `7` for work and service jobs, such as installation or delivery. A service has no warehouse fields — stock, weight, and purchase price; sale prices are set in [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices).\n\nBitrix24 API: `catalog.product.service.*`\nScope: `catalog`\n\n## Operations\n\n- [Create a service](.\u002Fcatalog-services\u002Fcreate.md) — `POST \u002Fv1\u002Fcatalog-services`\n- [List services](.\u002Fcatalog-services\u002Flist.md) — `GET \u002Fv1\u002Fcatalog-services`\n- [Get a service](.\u002Fcatalog-services\u002Fget.md) — `GET \u002Fv1\u002Fcatalog-services\u002F:id`\n- [Update a service](.\u002Fcatalog-services\u002Fupdate.md) — `PATCH \u002Fv1\u002Fcatalog-services\u002F:id`\n- [Delete a service](.\u002Fcatalog-services\u002Fdelete.md) — `DELETE \u002Fv1\u002Fcatalog-services\u002F:id`\n- [Search services](.\u002Fcatalog-services\u002Fsearch.md) — `POST \u002Fv1\u002Fcatalog-services\u002Fsearch`\n- [Service fields](.\u002Fcatalog-services\u002Ffields.md) — `GET \u002Fv1\u002Fcatalog-services\u002Ffields`\n- [Aggregate services](.\u002Fcatalog-services\u002Faggregate.md) — `POST \u002Fv1\u002Fcatalog-services\u002Faggregate`\n\n## Key fields\n\n| Field | Description |\n|------|---------|\n| `name` | Service name |\n| `iblockId` | Product catalog ID — the catalog whose `productIblockId` is empty. Required when creating and in the list, search, and aggregation filter. List: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist) |\n| `iblockSectionId` | Catalog section ID. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `type` | Record type, always `7` for a service. Set by Bitrix24 |\n| `active` | Whether the service is active |\n| `available` | Whether the service is available for purchase. If the field is not passed on creation, the service is created with `false` |\n| `measure` | Unit of measure ID. List: [`GET \u002Fv1\u002Fcatalog-measures`](\u002Fdocs\u002Fentities\u002Fcatalog-measures\u002Flist) |\n| `vatIncluded` | Whether VAT is included in the price |\n\nFull list of fields — [`GET \u002Fv1\u002Fcatalog-services\u002Ffields`](.\u002Fcatalog-services\u002Ffields.md).\n\n## What to know before you start\n\n1. **Services are stored in the product catalog.** In [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist), it is the record without `productIblockId`. Its `id` is passed in `iblockId` when creating a service and in the list, search, and aggregation filter. With the `iblockId` of an offer catalog, list and create return `422 BITRIX_ERROR` with the text `productType is not allowed for this catalog`.\n2. **List, search, and aggregation require a catalog filter.** Without `filter[iblockId]`, the request is rejected with `400 MISSING_REQUIRED_FILTER` before Bitrix24 is called.\n3. **Only services are returned.** Each record comes with `type: 7`; regular catalog products are not included in the list. A `type` filter does not change the result set.\n4. **Field selection is strict.** If `select` is passed in a list or search, it must include `iblockId` and `id`, and an unknown name is rejected with `400 UNKNOWN_SELECT_FIELD`. Without `select`, the list returns a reduced set of fields, while a request for a single record by `id` returns the full set together with catalog properties of the form `propertyNNN`.\n5. **The minimum to create a service is `name` and `iblockId`.** Without `name`, the request is rejected with `422 BITRIX_ERROR` and the text `Required fields: name`; without `iblockId`, with the text `iblock is not catalog`.\n6. **Update and delete check that the `id` belongs to a service.** With the `id` of a regular product, `PATCH` and `DELETE` return `404 ENTITY_NOT_FOUND`, and the product stays unchanged.\n7. **Catalog properties in the field reference require `iblockId`.** On a successful Bitrix24 response, [`GET \u002Fv1\u002Fcatalog-services\u002Ffields?iblockId=25`](.\u002Fcatalog-services\u002Ffields.md) returns the 28 static fields together with the catalog properties `propertyNNN`, and the `fields_partial` warning is absent. Without the parameter, only the 28 static fields and the `fields_partial` warning come back. Property values come in the response for a single service; their names and types are also available in [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties\u002Flist).\n\n## Typical scenario\n\n1. Find the product catalog and its `id`: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist) — the record without `productIblockId`.\n2. Choose a section for the service: [`GET \u002Fv1\u002Fcatalog-sections?filter[iblockId]=25`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist).\n3. Create a service: [`POST \u002Fv1\u002Fcatalog-services`](.\u002Fcatalog-services\u002Fcreate.md).\n4. Set the service price — pass the service `id` in `productId`: [`POST \u002Fv1\u002Fcatalog-prices`](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Fcreate).\n5. View the services of a section: [`GET \u002Fv1\u002Fcatalog-services?filter[iblockId]=25&filter[iblockSectionId]=281`](.\u002Fcatalog-services\u002Flist.md).\n\n## Limits\n\n| Limit | Value |\n|-------|----------|\n| Maximum records per request | 5000 (`limit ≤ 5000`) |\n| Auto-pagination | enabled when `limit > 50` |\n| Batch requests | up to 50 operations in [`POST \u002Fv1\u002Fbatch`](\u002Fdocs\u002Fbatch) |\n| Rate limit | shared across the Vibecode API — see [Limits and optimization](\u002Fdocs\u002Foptimization) |\n\n## See also\n\n- [Catalog products](\u002Fdocs\u002Fentities\u002Fcatalog-products)\n- [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices)\n- [Catalogs](\u002Fdocs\u002Fentities\u002Fcatalogs)\n- [Catalog sections](\u002Fdocs\u002Fentities\u002Fcatalog-sections)\n- [Catalog measures](\u002Fdocs\u002Fentities\u002Fcatalog-measures)\n- [Catalog product properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","2026-10-13",{"catalog-services\u002Fcreate.md":7,"catalog-services\u002Flist.md":8,"catalog-services\u002Fget.md":9,"catalog-services\u002Fupdate.md":10,"catalog-services\u002Fdelete.md":11,"catalog-services\u002Fsearch.md":12,"catalog-services\u002Ffields.md":13,"catalog-services\u002Faggregate.md":14},"## Create a service\n\n`POST \u002Fv1\u002Fcatalog-services`\n\nCreates a service in the product catalog. Fields are passed flat at the root of the JSON.\n\n## Request fields (body)\n\n| Field | Type | Required | Description |\n|------|-----|:-----:|---------|\n| `name` | string | yes | Service name |\n| `iblockId` | number | yes | Product catalog ID — a catalog whose `productIblockId` is not filled in. List: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist) |\n| `iblockSectionId` | number | no | Catalog section ID. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `iblockSection` | array | no | Array of catalog section IDs, if the service belongs to several sections. The first ID of the array becomes the main section `iblockSectionId`. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `active` | boolean | no | Whether the service is active. Defaults to `true` |\n| `available` | boolean | no | Whether the service is available for purchase. Defaults to `false` |\n| `measure` | number | no | Unit of measure ID. List: [`GET \u002Fv1\u002Fcatalog-measures`](\u002Fdocs\u002Fentities\u002Fcatalog-measures\u002Flist) |\n| `vatIncluded` | boolean | no | VAT is included in the price |\n| `vatId` | number | no | VAT rate ID. List: [`GET \u002Fv1\u002Fcatalog-vat-rates`](\u002Fdocs\u002Fentities\u002Fcatalog-vat-rates\u002Flist) |\n| `code` | string | no | Symbolic code |\n| `xmlId` | string | no | External code. If omitted, the response returns the service `id` as a string in this field |\n| `sort` | number | no | Sort order. Defaults to `500` |\n| `previewText` | string | no | Preview text |\n| `previewTextType` | string | no | Preview text format: `text` or `html`. Defaults to `text` |\n| `detailText` | string | no | Detailed description |\n| `detailTextType` | string | no | Detailed description format: `text` or `html`. Defaults to `text` |\n| `previewPicture` | object | no | Preview picture |\n| `detailPicture` | object | no | Detail picture |\n| `propertyNNN` | string | no | Catalog property value, where `NNN` is the property `id` from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties\u002Flist). A string property accepts a string: `\"property301\": \"A-100\"` |\n\nFields marked `readonly: true` in [Service fields](.\u002Ffields.md) are filled in by the system — for example, `type` and `createdBy`. Such a field in the body is rejected with `400 READONLY_FIELD`. The sale price is set separately via [`POST \u002Fv1\u002Fcatalog-prices`](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Fcreate) with the service `id` in `productId`.\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"name\": \"Air conditioner installation\",\n    \"iblockId\": 25,\n    \"iblockSectionId\": 281,\n    \"measure\": 9,\n    \"vatIncluded\": true\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"name\": \"Air conditioner installation\",\n    \"iblockId\": 25,\n    \"iblockSectionId\": 281,\n    \"measure\": 9,\n    \"vatIncluded\": true\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services', {\n  method: 'POST',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    name: 'Air conditioner installation',\n    iblockId: 25,\n    iblockSectionId: 281,\n    measure: 9,\n    vatIncluded: true,\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log('Service ID:', data.id)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services', {\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    name: 'Air conditioner installation',\n    iblockId: 25,\n    iblockSectionId: 281,\n    measure: 9,\n    vatIncluded: true,\n  }),\n})\n\nconst { success, data } = await res.json()\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data` | object | Full object of the created service. The field set is the same as in [`GET \u002Fv1\u002Fcatalog-services\u002F:id`](.\u002Fget.md) |\n\n## Response example\n\nThe main fields are shown.\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"id\": 7231,\n    \"iblockId\": 25,\n    \"iblockSectionId\": 281,\n    \"iblockSection\": [281],\n    \"name\": \"Air conditioner installation\",\n    \"type\": 7,\n    \"active\": true,\n    \"available\": false,\n    \"code\": null,\n    \"xmlId\": \"7231\",\n    \"sort\": 500,\n    \"measure\": 9,\n    \"vatId\": null,\n    \"vatIncluded\": true,\n    \"previewText\": null,\n    \"previewTextType\": \"text\",\n    \"createdBy\": 1295,\n    \"modifiedBy\": 1295,\n    \"dateCreate\": \"2026-10-08T20:50:59.000Z\",\n    \"timestampX\": \"2026-10-08T20:50:59.000Z\"\n  }\n}\n```\n\n## Error response example\n\n422 — the name was not passed:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"BITRIX_ERROR\",\n    \"message\": \"Required fields: name\",\n    \"b24Code\": \"0\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|----------|\n| 422 | `BITRIX_ERROR` | `name` was not passed (`Required fields: name`) |\n| 422 | `BITRIX_ERROR` | `iblockId` was not passed, or no catalog with this `iblockId` exists (`iblock is not catalog`) |\n| 422 | `BITRIX_ERROR` | `iblockId` contains the ID of an offers catalog rather than a product catalog (`productType is not allowed for this catalog`) |\n| 400 | `EMPTY_CREATE_BODY` | The request body is empty |\n| 400 | `NESTED_FIELDS_ENVELOPE` | The body contains an object under `fields` instead of root-level fields; the service is not created |\n| 400 | `READONLY_FIELD` | The body contains a field filled in by the system, for example `type` or `createdBy` |\n| 403 | `SCOPE_DENIED` | The key lacks the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\n\nFull list of common API errors — [Errors](\u002Fdocs\u002Ferrors).\n\n## Known specifics\n\n**A new service is not available for purchase.** If `available` is not passed, the service is created with `available: false`. To make the service sellable right away, pass `\"available\": true` on creation or later in [`PATCH \u002Fv1\u002Fcatalog-services\u002F:id`](.\u002Fupdate.md).\n\n**Unknown scalar fields are not rejected.** A name that is not among the service fields is skipped in the create body: the service is created without it, and the response is `201`. An object under `fields` is rejected with `400 NESTED_FIELDS_ENVELOPE` before creation — send fields at the JSON root. Check names against [Service fields](.\u002Ffields.md).\n\n## See also\n\n- [List services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Get a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fget)\n- [Update a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fupdate)\n- [Delete a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fdelete)\n- [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices)\n- [Catalog sections](\u002Fdocs\u002Fentities\u002Fcatalog-sections)\n- [Batch](\u002Fdocs\u002Fbatch)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","## List services\n\n`GET \u002Fv1\u002Fcatalog-services`\n\nReturns the services of a product catalog with support for filtering, sorting, field selection, and pagination.\n\nRequires the `filter[iblockId]` filter — the product catalog ID. Without this filter, the request is rejected with `400`.\n\nThe `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` fields are not accepted in `filter`: the request returns `400 UNKNOWN_FILTER_FIELD` before calling Bitrix24. These dates remain available in `select` and responses.\n\n## Parameters\n\n| Parameter | Type | Required | Default | Description |\n|----------|-----|:-----:|-----------|---------|\n| `filter` | object | yes | — | Filtering by the fields of [`GET \u002Fv1\u002Fcatalog-services\u002Ffields`](.\u002Ffields.md). The `filter[iblockId]` key is required — the product catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist); this catalog has an empty `productIblockId`. `filter[iblockSectionId]` with an ID from [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) selects the services of one section.\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering). Example: `?filter[iblockId]=25&filter[iblockSectionId]=281` |\n| `select` | string | no | — | Comma-separated field selection: `?select=iblockId,id,name,code,sort`. If the parameter is passed, it must include `iblockId` and `id`. Without `select`, the response carries a reduced set of fields: the symbolic code, external code, sort order, VAT rate, texts, images, and catalog properties are returned only when listed explicitly — [Service fields](.\u002Ffields.md) lists which ones. A catalog property is requested by its own name: `?select=iblockId,id,name,property301` |\n| `sort` | string | no | — | Sort field. The `-` prefix means descending: `?sort=-id` |\n| `limit` | number | no | `50` | Number of records (up to 5000) |\n| `offset` | number | no | `0` | Offset from the start of the selection |\n\nFor `limit > 50`, the response is automatically assembled from several pages on the server side. The maximum is 5000 records per call. If more records match the filter than were returned, `meta.hasMore` is `true`.\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -g \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services?filter[iblockId]=25&filter[iblockSectionId]=281&limit=10\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\"\n```\n\n### curl — OAuth application\n\n```bash\ncurl -g \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services?filter[iblockId]=25&filter[iblockSectionId]=281&limit=10\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\"\n```\n\n### JavaScript — personal key\n\n```javascript\nconst params = new URLSearchParams({\n  'filter[iblockId]': '25',\n  'filter[iblockSectionId]': '281',\n  limit: '10',\n})\nconst res = await fetch(`https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services?${params}`, {\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nconst { success, data, meta } = await res.json()\nconsole.log(`Services: ${meta.total}`)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst params = new URLSearchParams({\n  'filter[iblockId]': '25',\n  'filter[iblockSectionId]': '281',\n  limit: '10',\n})\nconst res = await fetch(`https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services?${params}`, {\n  headers: {\n    'X-Api-Key': 'YOUR_APP_KEY',\n    'Authorization': 'Bearer USER_SESSION_TOKEN',\n  },\n})\n\nconst { success, data, meta } = await res.json()\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data` | array | Array of services. For the fields of each item, see [Service fields](.\u002Ffields.md) |\n| `meta.total` | number | Total number of records matching the filter |\n| `meta.hasMore` | boolean | Whether there are more records beyond `limit` |\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": [\n    {\n      \"active\": true,\n      \"available\": false,\n      \"bundle\": false,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T20:50:59.000Z\",\n      \"iblockId\": 25,\n      \"iblockSectionId\": 281,\n      \"id\": 7231,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Air conditioner installation\",\n      \"timestampX\": \"2026-10-08T20:50:59.000Z\",\n      \"type\": 7,\n      \"vatIncluded\": true\n    },\n    {\n      \"active\": true,\n      \"available\": false,\n      \"bundle\": false,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T20:51:00.000Z\",\n      \"iblockId\": 25,\n      \"iblockSectionId\": 281,\n      \"id\": 7233,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Air conditioner cleaning\",\n      \"timestampX\": \"2026-10-08T20:51:00.000Z\",\n      \"type\": 7,\n      \"vatIncluded\": true\n    }\n  ],\n  \"meta\": {\n    \"total\": 2,\n    \"hasMore\": false\n  }\n}\n```\n\n## Error response example\n\n400 — the required filter was not passed:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"MISSING_REQUIRED_FILTER\",\n    \"message\": \"GET \u002Fv1\u002Fcatalog-services requires filter fields: iblockId. Example: GET \u002Fv1\u002Fcatalog-services?filter[iblockId]=...\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 400 | `MISSING_REQUIRED_FILTER` | The required `filter[iblockId]` filter was not passed. The request is rejected before Bitrix24 is called — the message contains the name of the missing field and a call example |\n| 400 | `UNKNOWN_FILTER_FIELD` | Unknown filter field or a date field: `dateCreate`, `timestampX`, `dateActiveFrom`, `dateActiveTo`. The message contains the list of available fields |\n| 400 | `UNKNOWN_SORT_FIELD` | Sorting by a field the service does not have. The message contains the list of available fields |\n| 400 | `UNKNOWN_SELECT_FIELD` | `select` contains a name the service does not have. The message contains the list of available fields |\n| 400 | `SELECT_FIELD_NOT_RETURNED` | `select` contains `iblockSection` — this name is not supported in the selection. The main section is returned in `iblockSectionId` |\n| 422 | `BITRIX_ERROR` | `select` does not include `iblockId` (`Required select fields: iblockId`) |\n| 422 | `BITRIX_ERROR` | `select` does not include `id` (`Required select fields: id`) |\n| 422 | `BITRIX_ERROR` | `filter[iblockId]` holds the ID of an offer catalog, not a product catalog (`productType is not allowed for this catalog`) |\n| 422 | `BITRIX_ERROR` | There is no catalog with the specified `iblockId` (`iblock is not catalog`) |\n| 403 | `SCOPE_DENIED` | The API key does not have the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\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**A `type` filter does not change the result set.** The response contains only services, each with `type` equal to `7`. A request with `filter[type]=1` returns the same set of services as without this filter — regular catalog products cannot be read through this endpoint.\n\n## See also\n\n- [Create a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fcreate)\n- [Get a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fget)\n- [Search services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fsearch)\n- [Service fields](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Ffields)\n- [Catalog sections](\u002Fdocs\u002Fentities\u002Fcatalog-sections)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","## Get a service\n\n`GET \u002Fv1\u002Fcatalog-services\u002F:id`\n\nReturns a single catalog service by identifier, together with its catalog sections and catalog properties.\n\nThe response contains more fields than the [list](.\u002Flist.md): in addition to the reduced set, it returns the symbolic code, external code, sort order, VAT rate, texts, images, and catalog properties of the form `propertyNNN`.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `id` (path) | number | yes | Service identifier. List: [`GET \u002Fv1\u002Fcatalog-services`](.\u002Flist.md) |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7233\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\"\n```\n\n### curl — OAuth application\n\n```bash\ncurl \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7233\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\"\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7233', {\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nconst { success, data } = await res.json()\nconsole.log('Service:', data.name, 'section:', data.iblockSectionId)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7233', {\n  headers: {\n    'X-Api-Key': 'YOUR_APP_KEY',\n    'Authorization': 'Bearer USER_SESSION_TOKEN',\n  },\n})\n\nconst { success, data } = await res.json()\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data` | object | Service object. All fields — see [Service fields](.\u002Ffields.md) |\n\nOn top of the fields the list returns, a single record contains:\n\n- `code` — symbolic code\n- `xmlId` — external code. If it was not set on creation, it contains the service `id` as a string\n- `sort` — sort order\n- `vatId` — VAT rate ID. List: [`GET \u002Fv1\u002Fcatalog-vat-rates`](\u002Fdocs\u002Fentities\u002Fcatalog-vat-rates\u002Flist)\n- `previewText`, `detailText`, `previewTextType`, `detailTextType` — texts and their format\n- `previewPicture`, `detailPicture` — images\n- `iblockSection` — an array of IDs of all catalog sections the service belongs to\n- catalog properties of the form `propertyNNN`, where `NNN` is the property `id` from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties\u002Flist). An empty property is returned as `null`, a filled string property as an object `{ value, valueId }`\n\nFields with an empty value are returned as `null`.\n\n## Response example\n\nThe main fields are shown.\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"id\": 7233,\n    \"iblockId\": 25,\n    \"iblockSectionId\": 281,\n    \"iblockSection\": [281],\n    \"name\": \"Air conditioner cleaning\",\n    \"type\": 7,\n    \"active\": true,\n    \"available\": false,\n    \"code\": \"cleaning\",\n    \"xmlId\": \"7233\",\n    \"sort\": 200,\n    \"measure\": 9,\n    \"vatId\": null,\n    \"vatIncluded\": true,\n    \"previewText\": \"Filter and heat exchanger cleaning\",\n    \"previewTextType\": \"text\",\n    \"detailText\": null,\n    \"detailTextType\": \"text\",\n    \"previewPicture\": null,\n    \"detailPicture\": null,\n    \"property295\": \"N\",\n    \"property301\": null,\n    \"createdBy\": 1295,\n    \"modifiedBy\": 1295,\n    \"dateCreate\": \"2026-10-08T20:51:00.000Z\",\n    \"timestampX\": \"2026-10-08T20:51:00.000Z\"\n  }\n}\n```\n\n## Error response example\n\n404 — no service with this `id` exists:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"ENTITY_NOT_FOUND\",\n    \"message\": \"service does not exist.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 404 | `ENTITY_NOT_FOUND` | No record with the specified `id` exists (`service does not exist.`) |\n| 404 | `ENTITY_NOT_FOUND` | A record with the specified `id` exists but is not a service — for example, a regular catalog product (`catalogService \u003Cid> not found`) |\n| 400 | `INVALID_PARAMS` | `id` is not a non-negative integer (`id must be a non-negative integer`) |\n| 403 | `SCOPE_DENIED` | The API key lacks the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\n\nFull list of common API errors — [Errors](\u002Fdocs\u002Ferrors).\n\n## Known specifics\n\n**`iblockSection` is returned only in the single-record response.** The array of the service's sections is present in the `GET \u002Fv1\u002Fcatalog-services\u002F:id` response, while the list and search reject this name in `select` with `400 SELECT_FIELD_NOT_RETURNED`. In the list, read the main section from `iblockSectionId`.\n\n## See also\n\n- [Create a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fcreate)\n- [Update a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fupdate)\n- [Delete a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fdelete)\n- [List services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Service fields](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Ffields)\n- [Catalog product properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","## Update a service\n\n`PATCH \u002Fv1\u002Fcatalog-services\u002F:id`\n\nUpdates an existing catalog service. Fields are passed flat at the root of the JSON; pass only the fields you change — the rest keep their current values.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `id` (path) | number | yes | Service identifier. List: [`GET \u002Fv1\u002Fcatalog-services`](.\u002Flist.md) |\n\n## Request fields (body)\n\nThe main writable fields are `name`, `active`, `available`, and `iblockSectionId`.\n\n| Field | Type | Required | Description |\n|------|-----|:-----:|---------|\n| `name` | string | no | Service name |\n| `active` | boolean | no | Whether the service is active |\n| `available` | boolean | no | Whether the service is available for purchase |\n| `iblockSectionId` | number | no | Main catalog section ID. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `iblockSection` | array | no | Array of IDs of all catalog sections the service belongs to. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `measure` | number | no | Unit of measure ID. List: [`GET \u002Fv1\u002Fcatalog-measures`](\u002Fdocs\u002Fentities\u002Fcatalog-measures\u002Flist) |\n| `vatIncluded` | boolean | no | Whether VAT is included in the price |\n| `vatId` | number | no | VAT rate ID. List: [`GET \u002Fv1\u002Fcatalog-vat-rates`](\u002Fdocs\u002Fentities\u002Fcatalog-vat-rates\u002Flist) |\n| `code` | string | no | Symbolic code |\n| `xmlId` | string | no | External code |\n| `sort` | number | no | Sort order |\n| `previewText` | string | no | Preview text |\n| `previewTextType` | string | no | Preview text format: `text` or `html` |\n| `detailText` | string | no | Detailed description |\n| `detailTextType` | string | no | Detailed description format: `text` or `html` |\n| `previewPicture` | object | no | Preview picture |\n| `detailPicture` | object | no | Detail picture |\n| `propertyNNN` | object | no | Catalog property value, where `NNN` is the property `id` from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties\u002Flist). Passed as an object: `\"property301\": { \"value\": \"A-300\" }` |\n\n**The body must contain at least one writable field.** An empty `PATCH` is rejected with `400 EMPTY_UPDATE_BODY`, and a body with only unknown field names is rejected with `400 NO_RECOGNIZED_UPDATE_FIELDS`.\n\nThe `iblockId` field is not accepted on update: the catalog is set on creation. Fields marked `readonly: true` in [Service fields](.\u002Ffields.md), for example `type` and `createdBy`, are filled in by the system. Any of these fields in the body is rejected with `400 READONLY_FIELD`.\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X PATCH \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7231\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"name\": \"Turnkey air conditioner installation\"\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X PATCH \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7231\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"name\": \"Turnkey air conditioner installation\"\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7231', {\n  method: 'PATCH',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    name: 'Turnkey air conditioner installation',\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log('Name:', data.name)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7231', {\n  method: 'PATCH',\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    name: 'Turnkey air conditioner installation',\n  }),\n})\n\nconst { success, data } = await res.json()\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data` | object | Full object of the updated service. The field set is the same as in [`GET \u002Fv1\u002Fcatalog-services\u002F:id`](.\u002Fget.md) |\n\n## Response example\n\nThe main fields are shown.\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"id\": 7231,\n    \"iblockId\": 25,\n    \"iblockSectionId\": 281,\n    \"iblockSection\": [281],\n    \"name\": \"Turnkey air conditioner installation\",\n    \"type\": 7,\n    \"active\": true,\n    \"available\": false,\n    \"xmlId\": \"7231\",\n    \"sort\": 500,\n    \"measure\": 9,\n    \"vatIncluded\": true,\n    \"dateCreate\": \"2026-10-08T20:50:59.000Z\",\n    \"timestampX\": \"2026-10-08T20:52:26.000Z\"\n  }\n}\n```\n\n## Error response example\n\n404 — no service with the specified `id` exists:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"ENTITY_NOT_FOUND\",\n    \"message\": \"service does not exist.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|----------|\n| 404 | `ENTITY_NOT_FOUND` | No record with the specified `id` exists, or the record is not a service — for example, a regular catalog product (`service does not exist.`). The record is not changed |\n| 400 | `EMPTY_UPDATE_BODY` | The request body is empty — pass at least one field |\n| 400 | `NESTED_FIELDS_ENVELOPE` | The body contains an object under `fields` instead of root-level fields; the service is not updated |\n| 400 | `NO_RECOGNIZED_UPDATE_FIELDS` | The body contains no recognized writable field. The message lists the allowed ones |\n| 400 | `READONLY_FIELD` | The body contains a field that is not accepted on update: `iblockId` or a read-only field, for example `type` |\n| 403 | `SCOPE_DENIED` | The key lacks the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\n\nFull list of common API errors — [Errors](\u002Fdocs\u002Ferrors).\n\n## Known specifics\n\n**A catalog property changes only when passed as an object.** A `PATCH` with a string — `\"property301\": \"A-200\"` — or with `null` returns `200`, but the property value does not change. Pass the new value as an object `{ \"value\": \"...\" }`. When you create a service, a string property also accepts a plain string.\n\n**`iblockSectionId` replaces all of the service's sections.** If the service belongs to several sections, a `PATCH` with `iblockSectionId` leaves it only in the specified section — the `iblockSection` array in the response contains one ID. To keep several sections, pass them as a full array in `iblockSection`.\n\n**Unknown scalar field names next to known ones are ignored.** A body with `name` and an unknown scalar field name returns `200`: `name` is written, and the unknown name is dropped without a warning. An object under `fields` is rejected with `400 NESTED_FIELDS_ENVELOPE` before the update — send service fields at the JSON root.\n\n## See also\n\n- [Create a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fcreate)\n- [List services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Get a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fget)\n- [Delete a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fdelete)\n- [Service fields](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Ffields)\n- [Catalog product properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties)\n- [Batch](\u002Fdocs\u002Fbatch)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","## Delete a service\n\n`DELETE \u002Fv1\u002Fcatalog-services\u002F:id`\n\nDeletes a catalog service by ID. A deleted service cannot be restored through the API — create a new one if needed.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `id` (path) | number | yes | Service ID. List: [`GET \u002Fv1\u002Fcatalog-services`](.\u002Flist.md) |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X DELETE \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7235\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\"\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X DELETE \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7235\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\"\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7235', {\n  method: 'DELETE',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nif (res.status === 204) {\n  console.log('Service deleted')\n}\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002F7235', {\n  method: 'DELETE',\n  headers: {\n    'X-Api-Key': 'YOUR_APP_KEY',\n    'Authorization': 'Bearer USER_SESSION_TOKEN',\n  },\n})\n\nif (res.status === 204) {\n  console.log('Service deleted')\n}\n```\n\n## Response\n\nA successful deletion returns HTTP status `204 No Content` with an empty body — check the status code to confirm success.\n\n## Response example\n\n```http\nHTTP\u002F1.1 204 No Content\n```\n\n## Error response example\n\n404 — no service with the given `id`:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"ENTITY_NOT_FOUND\",\n    \"message\": \"service does not exist.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|----------|\n| 404 | `ENTITY_NOT_FOUND` | No record with the given `id` exists, including on a repeated deletion, or the record is not a service — for example, a regular catalog product (`service does not exist.`). The record is not deleted in that case |\n| 403 | `SCOPE_DENIED` | The key does not have the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\n\nFull list of common API errors — [Errors](\u002Fdocs\u002Ferrors).\n\n## See also\n\n- [Create a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fcreate)\n- [List services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Get a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fget)\n- [Update a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fupdate)\n- [Catalog products](\u002Fdocs\u002Fentities\u002Fcatalog-products)\n- [Batch](\u002Fdocs\u002Fbatch)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","## Search services\n\n`POST \u002Fv1\u002Fcatalog-services\u002Fsearch`\n\nSearches the services of a product catalog by conditions passed in the request body.\n\nUnlike [`GET \u002Fv1\u002Fcatalog-services`](.\u002Flist.md), the `filter`, `select`, `sort`, `limit`, and `offset` parameters are passed in the JSON body rather than in the query string — conditions on several fields are set as a nested structure. Like the list, search requires `filter.iblockId` — without this field, the request returns `400`. The response format is the same as for the list.\n\nThe `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` fields are not accepted in `filter`: the request returns `400 UNKNOWN_FILTER_FIELD` before calling Bitrix24. These dates remain available in `select` and responses.\n\n## Request fields (body)\n\n| Parameter | Type | Required | Default | Description |\n|----------|-----|:-----:|-----------|---------|\n| `filter` | object | yes | — | Filtering by the fields of [`GET \u002Fv1\u002Fcatalog-services\u002Ffields`](.\u002Ffields.md). The `iblockId` key is required — the product catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist); this catalog has an empty `productIblockId`. `iblockSectionId` with an ID from [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) selects the services of one section.\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering). Example: `{ \"iblockId\": 25, \"iblockSectionId\": 281 }` |\n| `select` | string[] | no | — | Field selection: `[\"iblockId\", \"id\", \"name\", \"code\"]`. If the parameter is passed, it must include `iblockId` and `id`. Without `select`, the response carries a reduced set of fields: the symbolic code, external code, sort order, VAT rate, texts, images, and catalog properties are returned only when listed explicitly — [Service fields](.\u002Ffields.md) lists which ones |\n| `sort` | string | no | — | Sort field. The `-` prefix means descending: `\"-id\"` |\n| `limit` | number | no | `50` | Number of records (up to 5000) |\n| `offset` | number | no | `0` | Offset from the start of the result set |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Fsearch\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"iblockId\": 25, \"iblockSectionId\": 281 },\n    \"limit\": 10\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Fsearch\" \\\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\": { \"iblockId\": 25, \"iblockSectionId\": 281 },\n    \"limit\": 10\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Fsearch', {\n  method: 'POST',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    filter: { iblockId: 25, iblockSectionId: 281 },\n    limit: 10,\n  }),\n})\n\nconst { success, data, meta } = await res.json()\nconsole.log(`Found: ${meta.total}`)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Fsearch', {\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: { iblockId: 25, iblockSectionId: 281 },\n    limit: 10,\n  }),\n})\n\nconst { success, data, meta } = await res.json()\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data` | array | Array of services. For the fields of each item, see [Service fields](.\u002Ffields.md) |\n| `meta.total` | number | How many records matched the filter |\n| `meta.hasMore` | boolean | Whether there are more records beyond `limit` |\n| `meta.durationMs` | number | Request duration in milliseconds |\n\nThe `meta` fields sit next to `data`, not inside it. Paginate using `meta.hasMore`: a `data` array whose length equals `limit` can still be the last page.\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": [\n    {\n      \"active\": true,\n      \"available\": false,\n      \"bundle\": false,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T20:50:59.000Z\",\n      \"iblockId\": 25,\n      \"iblockSectionId\": 281,\n      \"id\": 7231,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Air conditioner installation\",\n      \"timestampX\": \"2026-10-08T20:50:59.000Z\",\n      \"type\": 7,\n      \"vatIncluded\": true\n    },\n    {\n      \"active\": true,\n      \"available\": false,\n      \"bundle\": false,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T20:51:00.000Z\",\n      \"iblockId\": 25,\n      \"iblockSectionId\": 281,\n      \"id\": 7233,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Air conditioner cleaning\",\n      \"timestampX\": \"2026-10-08T20:51:00.000Z\",\n      \"type\": 7,\n      \"vatIncluded\": true\n    }\n  ],\n  \"meta\": {\n    \"total\": 2,\n    \"hasMore\": false,\n    \"durationMs\": 165\n  }\n}\n```\n\n## Error response example\n\n400 — the required filter was not passed:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"MISSING_REQUIRED_FILTER\",\n    \"message\": \"POST \u002Fv1\u002Fcatalog-services\u002Fsearch requires filter fields: iblockId. Example body: { \\\"filter\\\": {\\\"iblockId\\\":\\\"...\\\"} }\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 400 | `MISSING_REQUIRED_FILTER` | The required `filter.iblockId` filter was not passed. The request is rejected before Bitrix24 is called — the message contains the name of the missing field and a request body example |\n| 400 | `UNKNOWN_FILTER_FIELD` | Unknown filter field or a date field: `dateCreate`, `timestampX`, `dateActiveFrom`, `dateActiveTo`. The message contains the list of available fields |\n| 400 | `UNKNOWN_SORT_FIELD` | Sorting by a field the service does not have. The message contains the list of available fields |\n| 400 | `UNKNOWN_SELECT_FIELD` | `select` contains a name the service does not have. The message contains the list of available fields |\n| 400 | `SELECT_FIELD_NOT_RETURNED` | `select` contains `iblockSection` — this name is not supported in the selection. The main section comes in `iblockSectionId` |\n| 422 | `BITRIX_ERROR` | `select` does not include `iblockId` (`Required select fields: iblockId`) |\n| 422 | `BITRIX_ERROR` | `select` does not include `id` (`Required select fields: id`) |\n| 422 | `BITRIX_ERROR` | `filter.iblockId` holds the ID of an offer catalog, not a product catalog (`productType is not allowed for this catalog`) |\n| 422 | `BITRIX_ERROR` | There is no catalog with the specified `iblockId` (`iblock is not catalog`) |\n| 403 | `SCOPE_DENIED` | The API key does not have the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\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## See also\n\n- [List services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Create a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fcreate)\n- [Get a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fget)\n- [Service fields](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Ffields)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","## Service fields\n\n`GET \u002Fv1\u002Fcatalog-services\u002Ffields`\n\nReturns the catalog service field reference with labels, types, write flags and dictionaries of allowed values, plus the fields available for aggregation and the operations available in a batch request.\n\nThe reference contains 28 static service fields. Pass `?iblockId=\u003Ccatalog ID>` to also receive catalog properties of the form `propertyNNN` when Bitrix24 returns the dynamic field metadata successfully. Get the catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist). Property values arrive in the [`GET \u002Fv1\u002Fcatalog-services\u002F:id`](.\u002Fget.md) response; their names and types are also available from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties\u002Flist).\n\nThe `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` fields are not accepted in `filter`: the request returns `400 UNKNOWN_FILTER_FIELD` before calling Bitrix24. These dates remain available in `select` and responses.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `iblockId` (query) | number | no | Product catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist) — a catalog whose `productIblockId` is empty. With it, a successful response is extended with the catalog properties `propertyNNN`. Without it, and also when Bitrix24 refuses the dynamic field metadata (for example, no catalog has that ID), the response has 28 static fields and the `fields_partial` warning in `meta.warnings` |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Ffields?iblockId=25\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\"\n```\n\n### curl — OAuth application\n\n```bash\ncurl \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Ffields?iblockId=25\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\"\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Ffields?iblockId=25', {\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nconst { success, data } = await res.json()\nconsole.log('Fields:', Object.keys(data.fields).length)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Ffields?iblockId=25', {\n  headers: {\n    'X-Api-Key': 'YOUR_APP_KEY',\n    'Authorization': 'Bearer USER_SESSION_TOKEN',\n  },\n})\n\nconst { success, data } = await res.json()\n```\n\n## Response fields\n\n`data.fields` is an object keyed by field name; each value contains the type, the write flags, a display label and — for fields with a fixed set of values — their dictionary. The set of keys is broken down in the table after the field list. `data.aggregatable` lists the fields for `groupBy` in [aggregation](.\u002Faggregate.md). `data.batch` lists the operations available in a [batch request](\u002Fdocs\u002Fbatch).\n\n| Field | Type | RO | Description |\n|------|-----|:--:|---------|\n| `id` | number | yes | Service ID |\n| `name` | string | no | Service name. Required on create |\n| `active` | boolean | no | Whether the service is active |\n| `available` | boolean | no | Whether the service is available for purchase. Without an explicit value, the service is created with `false` |\n| `iblockId` | number | no¹ | Product catalog ID. List: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist). Required on create, set on create only |\n| `type` | number | yes | Record type, always `7` for a service. Set by Bitrix24 |\n| `iblockSectionId` | number \\| null | no | Primary catalog section ID. `null` — the service is not linked to a section. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `iblockSection` | object | no | Array of IDs of all catalog sections the service belongs to. Returned in the single-service response, not supported in the list `select`. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist) |\n| `measure` | number | no | Unit of measure ID. List: [`GET \u002Fv1\u002Fcatalog-measures`](\u002Fdocs\u002Fentities\u002Fcatalog-measures\u002Flist) |\n| `vatIncluded` | boolean | no | VAT is included in the price |\n| `vatId` | number | no | VAT rate ID. List: [`GET \u002Fv1\u002Fcatalog-vat-rates`](\u002Fdocs\u002Fentities\u002Fcatalog-vat-rates\u002Flist) |\n| `bundle` | boolean | yes | Whether the service is a bundle. Computed by Bitrix24 |\n| `code` | string \\| null | no | Symbolic code. `null` if not set |\n| `xmlId` | string | no | External identifier. If not set, equals the service `id` as a string |\n| `sort` | number | no | Sort order. Defaults to `500` |\n| `dateActiveFrom` | datetime | no | Activity start date. Not accepted in filters (`400 UNKNOWN_FILTER_FIELD`) |\n| `dateActiveTo` | datetime | no | Activity end date. Not accepted in filters (`400 UNKNOWN_FILTER_FIELD`) |\n| `createdBy` | number | yes | ID of the user who created the service. List: [`GET \u002Fv1\u002Fusers`](\u002Fdocs\u002Fentities\u002Fusers\u002Flist) |\n| `modifiedBy` | number | yes | ID of the user who last modified the service. List: [`GET \u002Fv1\u002Fusers`](\u002Fdocs\u002Fentities\u002Fusers\u002Flist) |\n| `dateCreate` | datetime | yes | Creation date. Not accepted in filters (`400 UNKNOWN_FILTER_FIELD`) |\n| `timestampX` | datetime | yes | Last modification date. Not accepted in filters (`400 UNKNOWN_FILTER_FIELD`) |\n| `previewText` | string | no | Preview text |\n| `previewTextType` | string | no | Preview text format: `text` or `html` |\n| `detailText` | string | no | Detailed description |\n| `detailTextType` | string | no | Detailed description format: `text` or `html` |\n| `previewPicture` | object | no | Preview image |\n| `detailPicture` | object | no | Detail image |\n| `priceType` | string | no | Declared in the reference, but not returned in the list or single-service responses. Service prices are set via [`POST \u002Fv1\u002Fcatalog-prices`](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Fcreate) |\n| `propertyNNN` | productproperty | no | Catalog property, where `NNN` is the property `id` from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties\u002Flist). Returned only with `iblockId`; multiple-value properties carry `multiple: true`. The type arrives from Bitrix24 as is and is not one of the five static-field types |\n\n¹ `iblockId` is writable on create only — in the reference it comes back with `readonly: false` and `createOnly: true`.\n\nField labels `label` and descriptions `description` arrive in English. Fields with a fixed set of values — `previewTextType` and `detailTextType` — also carry an `enum` dictionary: an array of `{ value, label }`. Send `value`; `label` is meant for display.\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data.fields.\u003Cname>.type` | string | Field type. For the 28 static fields — `number`, `string`, `boolean`, `datetime`, `object`. For the dynamic catalog fields returned with `iblockId` — the Bitrix24 type as is, for example `productproperty` for a catalog property |\n| `data.fields.\u003Cname>.label` | string | Short field label |\n| `data.fields.\u003Cname>.description` | string | Extended field description: its purpose, where to get the list of allowed values, write-time behavior |\n| `data.fields.\u003Cname>.readonly` | boolean | `true` — the field is filled by the system and is not accepted on create or update |\n| `data.fields.\u003Cname>.createOnly` | boolean | `true` — the field is accepted on create only; on `PATCH` it is rejected with `READONLY_FIELD`. Present on `iblockId` |\n| `data.fields.\u003Cname>.writeOnly` | boolean | `true` — the field is accepted on write. Present on `iblockSection` |\n| `data.fields.\u003Cname>.notReturned` | boolean | `true` — the field name is not supported in `select`: list and search reject it with `SELECT_FIELD_NOT_RETURNED`. Present on `iblockSection` |\n| `data.fields.\u003Cname>.nullable` | boolean | `true` — the field can come back as `null`. The key is present only on such fields |\n| `data.fields.\u003Cname>.multiple` | boolean | `true` — the dynamic catalog field stores several values and arrives as an array. The key is present only on such fields |\n| `data.fields.\u003Cname>.items` | array | List items of a dynamic field of type `enumeration`, as Bitrix24 returned them. The key is present only on such fields |\n| `data.fields.\u003Cname>.values` | array | Dictionary of a dynamic field's values: an array of `{ value, label }`. The key is present when Bitrix24 returned such a dictionary |\n| `data.fields.\u003Cname>.default` | number \\| string \\| boolean \\| null | Default value of a dynamic field. The key is present when Bitrix24 returned it as a scalar or `null` |\n| `data.aggregatable` | string[] | Fields for `groupBy` in [aggregation](.\u002Faggregate.md): `iblockSectionId` |\n| `data.batch` | string[] | Service operations available in a [batch request](\u002Fdocs\u002Fbatch): `create`, `update`, `delete` |\n| `meta.warnings` | array | Warnings. An element with `code: fields_partial` arrives without `iblockId` or when Bitrix24 does not return the dynamic field metadata |\n\n## Response example\n\nResponse without `iblockId`: static fields only, with the `fields_partial` warning.\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"fields\": {\n      \"id\": {\n        \"type\": \"number\",\n        \"readonly\": true,\n        \"label\": \"Service identifier\",\n        \"description\": \"Unique identifier of the catalog service.\"\n      },\n      \"iblockId\": {\n        \"type\": \"number\",\n        \"readonly\": false,\n        \"createOnly\": true,\n        \"label\": \"Catalog ID\",\n        \"description\": \"Catalog the service belongs to. Available values: GET \u002Fv1\u002Fcatalogs. Set on create only — changing it via PATCH is rejected, a service cannot be moved between catalogs.\"\n      },\n      \"type\": {\n        \"type\": \"number\",\n        \"readonly\": true,\n        \"label\": \"Service type\",\n        \"description\": \"Bitrix24 service type (7); set by Bitrix24 and read only.\"\n      },\n      \"iblockSectionId\": {\n        \"type\": \"number\",\n        \"readonly\": false,\n        \"nullable\": true,\n        \"label\": \"Catalog section ID\",\n        \"description\": \"Primary catalog section of the service; null when it is not linked to a section. Available values: GET \u002Fv1\u002Fcatalog-sections.\"\n      },\n      \"iblockSection\": {\n        \"type\": \"object\",\n        \"readonly\": false,\n        \"writeOnly\": true,\n        \"notReturned\": true,\n        \"label\": \"Catalog sections\",\n        \"description\": \"Array of catalog section IDs the service belongs to. Accepted on create and update only — on read the primary section arrives as the scalar iblockSectionId. Available values: GET \u002Fv1\u002Fcatalog-sections.\"\n      },\n      \"previewTextType\": {\n        \"type\": \"string\",\n        \"readonly\": false,\n        \"label\": \"Preview text format\",\n        \"description\": \"Format of previewText.\",\n        \"enum\": [\n          { \"value\": \"text\", \"label\": \"Plain text\" },\n          { \"value\": \"html\", \"label\": \"HTML\" }\n        ]\n      }\n    },\n    \"aggregatable\": [\n      \"iblockSectionId\"\n    ],\n    \"batch\": [\n      \"create\",\n      \"update\",\n      \"delete\"\n    ]\n  },\n  \"meta\": {\n    \"warnings\": [\n      {\n        \"code\": \"fields_partial\",\n        \"message\": \"Dynamic field metadata from Bitrix24 was unavailable; the response carries static schema fields only and some labels may be missing. Retry to obtain the complete set.\"\n      }\n    ]\n  }\n}\n```\n\nThe example is trimmed to six fields — one each for the `readonly` and `createOnly` flags, the computed `type`, `nullable`, the `writeOnly` + `notReturned` pair and the `enum` dictionary. The static reference contains 28 fields; a successful response with `iblockId` contains the same static fields plus the catalog properties `propertyNNN`, and carries no `fields_partial` warning. For a catalog property, `type` arrives from Bitrix24 as is (`productproperty`), a multiple-value property carries `multiple: true`, and when Bitrix24 returned a value dictionary or a default, the keys `values`, `items` and `default` are present too.\n\n## Error response example\n\n401 — the key was not passed:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"MISSING_API_KEY\",\n    \"message\": \"API key required. Pass via X-Api-Key header.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 403 | `SCOPE_DENIED` | The API key does not have the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\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**Catalog properties require `iblockId`.** Without a catalog ID, the response contains static fields and `fields_partial`. With `?iblockId=\u003Ccatalog ID>`, a successful Bitrix24 response adds `propertyNNN` properties to the reference and the warning is absent. If Bitrix24 does not return the dynamic fields, the static response with the warning is preserved.\n\n**Some fields are not returned in the list by default.** The symbolic code `code`, the external code `xmlId`, the sort order `sort`, the VAT rate `vatId`, the texts, the images and the `propertyNNN` properties are not returned in the [`GET \u002Fv1\u002Fcatalog-services`](.\u002Flist.md) response without an explicit `?select=`. List the names you need in `select` to get them in the list. The single-service response, [`GET \u002Fv1\u002Fcatalog-services\u002F:id`](.\u002Fget.md), always returns them.\n\n## See also\n\n- [List services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Create a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fcreate)\n- [Get a service](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fget)\n- [Catalog product properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties)\n- [Catalog products](\u002Fdocs\u002Fentities\u002Fcatalog-products)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","## Aggregate services\n\n`POST \u002Fv1\u002Fcatalog-services\u002Faggregate`\n\nCounts the services of a product catalog with filtering and grouping by catalog section.\n\nThe `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` fields are not accepted in `filter`: the request returns `400 UNKNOWN_FILTER_FIELD` before calling Bitrix24. This endpoint does not support date-range filtering. Calculate aggregates for a specific period in your integration using records from the list endpoint, where these dates are available in `select` and responses.\n\n**Fields available for aggregation:**\n\n- `iblockSectionId` — catalog section, for `groupBy`\n\nA service has no numeric fields whose sum or average carries meaning. The `sum`, `avg`, `min`, `max` functions accept identifier fields — `id`, `iblockId`, `iblockSectionId`, `measure`, `vatId`, `createdBy`, `modifiedBy`, `type` — and the sort order `sort`. The main scenario is counting services with `count`, including by section.\n\n## Request fields (body)\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `filter` | object | yes | Filtering by the fields of [`GET \u002Fv1\u002Fcatalog-services\u002Ffields`](.\u002Ffields.md). The `iblockId` key is required — the product catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs\u002Flist). To select the services of one section, pass `iblockSectionId` with an ID from [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections\u002Flist).\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering) |\n| `aggregate` | array | no | Aggregations: `[{ \"field\": \"sort\", \"function\": \"max\" }]`. Functions: `sum`, `avg`, `min`, `max`, `count`. For `count`, the field is `\"*\"`. Without the parameter — `count` only |\n| `groupBy` | string \\| string[] | no | Field or array of fields to group by (up to 5). Value — `iblockSectionId` |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\u002Faggregate\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"iblockId\": 25, \"iblockSectionId\": 281 },\n    \"groupBy\": \"iblockSectionId\"\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\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\": { \"iblockId\": 25, \"iblockSectionId\": 281 },\n    \"groupBy\": \"iblockSectionId\"\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\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: { iblockId: 25, iblockSectionId: 281 },\n    groupBy: 'iblockSectionId',\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log(data.groups)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-services\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: { iblockId: 25, iblockSectionId: 281 },\n    groupBy: 'iblockSectionId',\n  }),\n})\n\nconst { success, data } = await res.json()\n```\n\n`groupBy` also accepts an array: `\"groupBy\": [\"iblockSectionId\"]`, up to 5 fields.\n\n## Other scenarios\n\nThe blocks below are request bodies.\n\nNumber of services in a catalog — a body without `aggregate` or `groupBy`, the fastest request, with no records fetched. The `iblockId` filter is required here too:\n\n```json\n{ \"filter\": { \"iblockId\": 25 } }\n```\n\nNumber of services in each catalog section — services without a section are collected into a group with `iblockSectionId: null`:\n\n```json\n{ \"filter\": { \"iblockId\": 25 }, \"groupBy\": \"iblockSectionId\" }\n```\n\n## Response fields\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data.count` | number | Total number of services matching the filter |\n| `data.aggregates` | object | Results of numeric functions: `{ \"sort\": { \"max\": 500 } }`. Without numeric functions — an empty object |\n| `data.groups` | array | Groups — present only with `groupBy`. Each element: grouping fields + `count` + `aggregates` |\n| `data.meta.totalRecords` | number | Total number of records |\n| `data.meta.recordsProcessed` | number | Number of processed records (up to 5000). For a request without numeric functions or `groupBy` — `0` |\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 numeric functions, no records are fetched and the flag is always `false` |\n| `data.meta.groupTotal` | number | Number of groups. Present only with `groupBy` |\n| `data.meta.groupsTruncated` | boolean | `true` if the number of groups was limited. Present only with `groupBy` |\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"count\": 2,\n    \"aggregates\": {},\n    \"groups\": [\n      {\n        \"iblockSectionId\": 281,\n        \"count\": 2,\n        \"aggregates\": {}\n      }\n    ],\n    \"meta\": {\n      \"totalRecords\": 2,\n      \"recordsProcessed\": 2,\n      \"truncated\": false,\n      \"groupTotal\": 1,\n      \"groupsTruncated\": false\n    }\n  }\n}\n```\n\nWithout `groupBy`, the `data.groups` field is absent from the response.\n\n## Error response example\n\n400 — a field outside the list available for grouping:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"INVALID_PARAMS\",\n    \"message\": \"groupBy field 'nonexistent' is not aggregatable on this entity. Available: iblockSectionId.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 400 | `MISSING_REQUIRED_FILTER` | The required `filter.iblockId` filter was not passed. This is checked before calling Bitrix24. The message contains an example request body |\n| 400 | `UNKNOWN_FILTER_FIELD` | Filter on a field that a service does not have, or on a date field: `dateCreate`, `timestampX`, `dateActiveFrom`, `dateActiveTo`. The message contains the list of available fields |\n| 400 | `INVALID_PARAMS` | The `groupBy` field is outside the available list — the message lists the allowed ones |\n| 400 | `INVALID_PARAMS` | A numeric function over an unknown field — `Field '\u003Cname>' not found. Available numeric fields: ...` |\n| 400 | `INVALID_PARAMS` | More than 5 fields passed in `groupBy` |\n| 422 | `BITRIX_ERROR` | `filter.iblockId` holds the ID of an offer catalog rather than a product catalog (`productType is not allowed for this catalog`) |\n| 403 | `SCOPE_DENIED` | The API key does not have the `catalog` scope |\n| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |\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**`count` is resolved in one request, grouping is not.** `count` without `groupBy` is computed in a single call regardless of the size of the result set. `groupBy` and the `sum`, `avg`, `min`, `max` functions load records page by page — up to 5000 — and compute the values on the server side. If more than 5000 records match the filter, `meta.truncated` is `true`, and the groups are built 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**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 services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Flist)\n- [Search services](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Fsearch)\n- [Service fields](\u002Fdocs\u002Fentities\u002Fcatalog-services\u002Ffields)\n- [Catalog sections](\u002Fdocs\u002Fentities\u002Fcatalog-sections)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n",1794739337003]