[{"data":1,"prerenderedAt":15},["ShallowReactive",2],{"docs-entities\u002Fcatalog-offers":3,"docs-tabs-entities\u002Fcatalog-offers":6},{"content":4,"lastmod":5},"# Catalog offers\n\nCatalog offers: list, retrieve, create, update, and delete. An offer describes a specific variant of a parent product — for example, a T-shirt size — and is stored in a separate offer catalog. The parent product is created in [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus), and the offer's sale prices are set in [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices).\n\nBitrix24 API: `catalog.product.offer.*`\nScope: `catalog`\n\n## Operations\n\n- [Create an offer](.\u002Fcatalog-offers\u002Fcreate.md) — `POST \u002Fv1\u002Fcatalog-offers`\n- [List offers](.\u002Fcatalog-offers\u002Flist.md) — `GET \u002Fv1\u002Fcatalog-offers`\n- [Get an offer](.\u002Fcatalog-offers\u002Fget.md) — `GET \u002Fv1\u002Fcatalog-offers\u002F:id`\n- [Update an offer](.\u002Fcatalog-offers\u002Fupdate.md) — `PATCH \u002Fv1\u002Fcatalog-offers\u002F:id`\n- [Delete an offer](.\u002Fcatalog-offers\u002Fdelete.md) — `DELETE \u002Fv1\u002Fcatalog-offers\u002F:id`\n- [Search offers](.\u002Fcatalog-offers\u002Fsearch.md) — `POST \u002Fv1\u002Fcatalog-offers\u002Fsearch`\n- [Offer fields](.\u002Fcatalog-offers\u002Ffields.md) — `GET \u002Fv1\u002Fcatalog-offers\u002Ffields`\n- [Aggregate offers](.\u002Fcatalog-offers\u002Faggregate.md) — `POST \u002Fv1\u002Fcatalog-offers\u002Faggregate`\n\n## Key fields\n\n| Field | Description |\n|------|---------|\n| `name` | Offer name |\n| `iblockId` | Offer catalog ID — the catalog that has `productIblockId` filled in. Required on create and in the list, search, and aggregation filters. List: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs) |\n| `parentId` | Link to the parent product. On create, it is passed as `{ \"value\": \"\u003CID>\" }`; the response returns it as an object `{ value, valueId }`. The parent product ID comes from [`GET \u002Fv1\u002Fcatalog-skus`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Flist) |\n| `type` | Record type, computed by Bitrix24: `4` — the offer is linked to a parent product, `5` — a free offer without `parentId` |\n| `active` | Whether the offer is active |\n| `measure` | Unit of measure ID. List: [`GET \u002Fv1\u002Fcatalog-measures`](\u002Fdocs\u002Fentities\u002Fcatalog-measures) |\n| `purchasingPrice` | Purchase price. Currency is in `purchasingCurrency` |\n| `quantity` | Stock balance |\n\nFull list of fields — [`GET \u002Fv1\u002Fcatalog-offers\u002Ffields`](.\u002Fcatalog-offers\u002Ffields.md).\n\n## What to know before you start\n\n1. **Offers have their own catalog.** Each product catalog has a separate offer catalog: in [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs) it has `productIblockId` — the product catalog ID — and `skuPropertyId` filled in. Its `iblockId` is passed when creating an offer and in the list, search, and aggregation filters. With the `iblockId` of the product catalog, the list and offer creation return `422 BITRIX_ERROR` with the message `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. **The link to the parent product is set on create.** The `parentId` field accepts `{ \"value\": \"\u003Cparent product ID>\" }`. Without it, a free offer with `type: 5` is created. The link cannot be changed: `PATCH` with `parentId` is rejected with `400 READONLY_FIELD`. To select all offers of one parent product, use `filter[parentId]=\u003CID>`.\n4. **The `type` filter accepts one exact value** — `4` or `5`. Other numbers, arrays, and operators are rejected with `400 UNSUPPORTED_FILTER`.\n5. **Field selection must include `iblockId` and `id`.** If a list or search request passes `select`, both fields are required in it. Without `select`, the list returns a reduced set of fields, while a single record by `id` returns the full set, together with catalog properties of the form `propertyNNN`.\n6. **The minimum for creation is `name` and `iblockId`.** If either is missing, the request is rejected with `400 MISSING_REQUIRED_FIELDS`.\n7. **Deleting a parent product deletes its offers.** After `DELETE \u002Fv1\u002Fcatalog-skus\u002F:id`, requests for that product's offers return `404 ENTITY_NOT_FOUND`.\n8. **`DELETE` does not check that the `id` belongs to an offer.** With the `id` of a regular product, the call returns `204` and deletes that product. Before deleting, check the record with [`GET \u002Fv1\u002Fcatalog-offers\u002F:id`](.\u002Fcatalog-offers\u002Fget.md): for the `id` of a regular product, it returns `404 ENTITY_NOT_FOUND`.\n\n## Typical scenario\n\n1. Find the offer catalog and its `iblockId`: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs) — the record with `productIblockId` filled in.\n2. Create or find a parent product: [`POST \u002Fv1\u002Fcatalog-skus`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Fcreate) \u002F [`GET \u002Fv1\u002Fcatalog-skus`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Flist).\n3. Create offers of the parent product: [`POST \u002Fv1\u002Fcatalog-offers`](.\u002Fcatalog-offers\u002Fcreate.md) with `parentId`.\n4. View the offers of the parent product: [`GET \u002Fv1\u002Fcatalog-offers?filter[iblockId]=27&filter[parentId]=7189`](.\u002Fcatalog-offers\u002Flist.md).\n5. Set the offer price — the offer `id` is passed in `productId`: [`POST \u002Fv1\u002Fcatalog-prices`](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Fcreate).\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 SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Catalog products](\u002Fdocs\u002Fentities\u002Fcatalog-products)\n- [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices)\n- [Catalogs](\u002Fdocs\u002Fentities\u002Fcatalogs)\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-08",{"catalog-offers\u002Fcreate.md":7,"catalog-offers\u002Flist.md":8,"catalog-offers\u002Fget.md":9,"catalog-offers\u002Fupdate.md":10,"catalog-offers\u002Fdelete.md":11,"catalog-offers\u002Fsearch.md":12,"catalog-offers\u002Ffields.md":13,"catalog-offers\u002Faggregate.md":14},"\n## Create an offer\n\n`POST \u002Fv1\u002Fcatalog-offers`\n\nCreates a product offer in the offers catalog and links it to a parent product. Fields are passed flat at the JSON root.\n\n## Request fields (body)\n\n| Field | Type | Required | Description |\n|------|-----|:-----:|---------|\n| `name` | string | yes | Offer name |\n| `iblockId` | number | yes | Offers catalog ID — the catalog that has `productIblockId` set. List: `GET \u002Fv1\u002Fcatalogs` |\n| `parentId` | object | no | Link to the parent product: `{ \"value\": \"\u003CID>\" }`, with the ID as a string. List: `GET \u002Fv1\u002Fcatalog-skus`. Without this field, a free offer with `type: 5` is created |\n| `active` | boolean | no | Whether the offer is active. Defaults to `true` |\n| `iblockSectionId` | number | no | Catalog section ID. List: `GET \u002Fv1\u002Fcatalog-sections` |\n| `measure` | number | no | Unit of measure ID. List: `GET \u002Fv1\u002Fcatalog-measures` |\n| `weight` | number | no | Weight of one product unit |\n| `vatIncluded` | boolean | no | VAT included in the price |\n| `canBuyZero` | boolean | no | Allow purchase when stock is zero |\n| `quantityTrace` | boolean | no | Enable quantity tracking |\n| `subscribe` | boolean | no | Allow subscription to the product |\n| `barcodeMulti` | boolean | no | Separate barcodes for product units |\n| `withoutOrder` | boolean | no | Available for ordering without stock on hand |\n| `purchasingPrice` | number | no | Purchase price |\n| `purchasingCurrency` | string | no | Purchase price currency. List: `GET \u002Fv1\u002Fcurrencies` |\n| `quantity` | number | no | Stock on hand |\n\nThe symbolic code, dimensions, texts, images and catalog properties `propertyNNN` are listed in [Offer fields](.\u002Ffields.md). The selling price is set separately — in [`POST \u002Fv1\u002Fcatalog-prices`](\u002Fdocs\u002Fentities\u002Fcatalog-prices\u002Fcreate) with the offer `id` in `productId`.\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"name\": \"Basic T-shirt, size M\",\n    \"iblockId\": 27,\n    \"parentId\": { \"value\": \"7189\" },\n    \"measure\": 9,\n    \"purchasingPrice\": 450,\n    \"purchasingCurrency\": \"USD\"\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\" \\\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\": \"Basic T-shirt, size M\",\n    \"iblockId\": 27,\n    \"parentId\": { \"value\": \"7189\" },\n    \"measure\": 9,\n    \"purchasingPrice\": 450,\n    \"purchasingCurrency\": \"USD\"\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers', {\n  method: 'POST',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    name: 'Basic T-shirt, size M',\n    iblockId: 27,\n    parentId: { value: '7189' },\n    measure: 9,\n    purchasingPrice: 450,\n    purchasingCurrency: 'USD',\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log('Offer ID:', data.id)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers', {\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: 'Basic T-shirt, size M',\n    iblockId: 27,\n    parentId: { value: '7189' },\n    measure: 9,\n    purchasingPrice: 450,\n    purchasingCurrency: 'USD',\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 offer. The field set is the same as in [`GET \u002Fv1\u002Fcatalog-offers\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\": 7191,\n    \"iblockId\": 27,\n    \"iblockSectionId\": null,\n    \"name\": \"Basic T-shirt, size M\",\n    \"parentId\": { \"value\": \"7189\", \"valueId\": \"3895\" },\n    \"type\": 4,\n    \"active\": true,\n    \"available\": true,\n    \"code\": null,\n    \"xmlId\": \"7191\",\n    \"measure\": 9,\n    \"purchasingPrice\": 450,\n    \"purchasingCurrency\": \"USD\",\n    \"quantity\": null,\n    \"quantityTrace\": true,\n    \"canBuyZero\": true,\n    \"vatIncluded\": false,\n    \"createdBy\": 1295,\n    \"dateCreate\": \"2026-10-08T18:41:55.000Z\",\n    \"timestampX\": \"2026-10-08T18:41:55.000Z\"\n  }\n}\n```\n\n## Error response example\n\n400 — a required field was not passed:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"MISSING_REQUIRED_FIELDS\",\n    \"message\": \"Body field \\\"name\\\" is required to create catalogOffer.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|----------|\n| 400 | `MISSING_REQUIRED_FIELDS` | A required field was not passed — the message names it. An empty body is rejected with a message about `iblockId` |\n| 400 | `READONLY_FIELD` | The body contains a read-only field, for example `available` |\n| 422 | `BITRIX_ERROR` | No parent product with the ID from `parentId` exists (`Parent product not found.`) |\n| 422 | `BITRIX_ERROR` | `iblockId` holds the ID of a product catalog, not an offers catalog (`productType is not allowed for this catalog`) |\n| 422 | `BITRIX_ERROR` | No catalog with the specified `iblockId` exists (`iblock is not catalog`) |\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## See also\n\n- [List offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Get an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fget)\n- [Update an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fupdate)\n- [Delete an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fdelete)\n- [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices)\n- [Batch](\u002Fdocs\u002Fbatch)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","\n## List offers\n\n`GET \u002Fv1\u002Fcatalog-offers`\n\nReturns product offers from the offer catalog with support for filtering, sorting, field selection, and pagination.\n\nRequires the `filter[iblockId]` filter — the offer catalog ID. Without this filter, the request is rejected with `400`.\n\nThe date fields `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` are not accepted in a filter: the Bitrix24 method does not apply date filters and would return every offer in the catalog. A request with such a filter is rejected with `400 UNKNOWN_FILTER_FIELD`.\n\n## Parameters\n\n| Parameter | Type | Required | Default | Description |\n|----------|-----|:-----:|-----------|---------|\n| `filter` | object | yes | — | Filtering by the fields of [`GET \u002Fv1\u002Fcatalog-offers\u002Ffields`](.\u002Ffields.md). The `filter[iblockId]` key is required — the offer catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs); this catalog has `productIblockId` filled in. Offers of one parent product are selected by `filter[parentId]` with an ID from [`GET \u002Fv1\u002Fcatalog-skus`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Flist). `filter[type]` accepts one exact value: `4` or `5`.\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering). Example: `?filter[iblockId]=27&filter[parentId]=7189` |\n| `select` | string | no | — | Comma-separated field selection: `?select=iblockId,id,name,parentId`. If the parameter is passed, it must include `iblockId` and `id`. Without `select`, the response carries a reduced set of fields: the symbolic code, dimensions, texts, images, and catalog properties are returned only when listed explicitly — [Offer fields](.\u002Ffields.md) lists which ones. A catalog property is requested by its own name: `?select=iblockId,id,name,property909` |\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-offers?filter[iblockId]=27&filter[parentId]=7189&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-offers?filter[iblockId]=27&filter[parentId]=7189&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]': '27',\n  'filter[parentId]': '7189',\n  limit: '10',\n})\nconst res = await fetch(`https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers?${params}`, {\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nconst { success, data, meta } = await res.json()\nconsole.log(`Offers: ${meta.total}`)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst params = new URLSearchParams({\n  'filter[iblockId]': '27',\n  'filter[parentId]': '7189',\n  limit: '10',\n})\nconst res = await fetch(`https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers?${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 offers. For the fields of each item, see [Offer 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| `meta.warnings` | array | Warnings. Present when `select` carries a name that cannot be selected: an item with `code: UNKNOWN_SELECT_FIELD` names it in `field`. The request still runs, and the name is absent from the response |\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": [\n    {\n      \"active\": true,\n      \"available\": true,\n      \"barcodeMulti\": false,\n      \"bundle\": false,\n      \"canBuyZero\": true,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T18:41:55.000Z\",\n      \"iblockId\": 27,\n      \"iblockSectionId\": null,\n      \"id\": 7191,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Basic T-shirt, size M\",\n      \"parentId\": { \"value\": \"7189\", \"valueId\": \"3895\" },\n      \"purchasingCurrency\": \"USD\",\n      \"purchasingPrice\": 450,\n      \"quantity\": null,\n      \"quantityTrace\": true,\n      \"subscribe\": true,\n      \"timestampX\": \"2026-10-08T18:41:55.000Z\",\n      \"type\": 4,\n      \"vatIncluded\": false,\n      \"weight\": null,\n      \"withoutOrder\": false\n    },\n    {\n      \"active\": true,\n      \"available\": true,\n      \"barcodeMulti\": false,\n      \"bundle\": false,\n      \"canBuyZero\": true,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T18:41:56.000Z\",\n      \"iblockId\": 27,\n      \"iblockSectionId\": null,\n      \"id\": 7193,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Basic T-shirt, size L\",\n      \"parentId\": { \"value\": \"7189\", \"valueId\": \"3897\" },\n      \"purchasingCurrency\": \"USD\",\n      \"purchasingPrice\": 490,\n      \"quantity\": 10,\n      \"quantityTrace\": true,\n      \"subscribe\": true,\n      \"timestampX\": \"2026-10-08T18:41:56.000Z\",\n      \"type\": 4,\n      \"vatIncluded\": false,\n      \"weight\": null,\n      \"withoutOrder\": false\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-offers requires filter fields: iblockId. Example: GET \u002Fv1\u002Fcatalog-offers?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 | `UNSUPPORTED_FILTER` | `filter[type]` carries a value other than `4` or `5`, an array, or an operator |\n| 400 | `UNKNOWN_FILTER_FIELD` | Filtering by a field the offer does not have, or by 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 offer does not have. The message contains the list of available fields |\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]` carries the ID of a product catalog rather than an offer catalog (`productType is not allowed for this catalog`) |\n| 422 | `BITRIX_ERROR` | No catalog exists 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**`parentId` is an object in the response and a number in the filter.** In `data` items, the link to the parent product arrives as an object: `value` is the parent product ID as a string, `valueId` is the internal ID of the link value. To select offers, pass the parent product ID itself in `filter[parentId]`. For an offer without a parent product, `parentId` is `null`.\n\n## See also\n\n- [Create an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fcreate)\n- [Get an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fget)\n- [Search offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fsearch)\n- [Offer fields](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Ffields)\n- [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","\n## Get an offer\n\n`GET \u002Fv1\u002Fcatalog-offers\u002F:id`\n\nReturns a single product offer by ID, together with its link to the parent product and its catalog properties.\n\nThe response contains more fields than the [list](.\u002Flist.md): in addition to the reduced set, it returns the symbolic code, dimensions, texts, images and catalog properties of the form `propertyNNN`.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `id` (path) | number | yes | Offer identifier. List: [`GET \u002Fv1\u002Fcatalog-offers`](.\u002Flist.md) |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191\" \\\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-offers\u002F7191\" \\\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-offers\u002F7191', {\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nconst { success, data } = await res.json()\nconsole.log('Offer:', data.name, 'parent product:', data.parentId?.value)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191', {\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 | Offer object. All fields — see [Offer fields](.\u002Ffields.md) |\n\nBeyond the fields the list returns, a single record contains:\n\n- `code` — symbolic code\n- `xmlId` — external code\n- `sort` — sort order\n- `vatId` — VAT rate ID. List: [`GET \u002Fv1\u002Fcatalog-vat-rates`](\u002Fdocs\u002Fentities\u002Fcatalog-vat-rates)\n- `width`, `height`, `length` — dimensions\n- `previewText`, `detailText`, `previewTextType`, `detailTextType` — texts and their format\n- `previewPicture`, `detailPicture` — images\n- `quantityReserved` — reserved stock\n- `recurSchemeLength`, `recurSchemeType`, `trialPriceId` — parameters for content sales in on-premise Bitrix24\n- catalog properties of the form `propertyNNN`, where `NNN` is the property `id` from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties). An empty property is returned as `null`; a filled multiple-value property is returned as an array of `{ value, valueId }` objects\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\": 7191,\n    \"iblockId\": 27,\n    \"iblockSectionId\": null,\n    \"name\": \"Basic T-shirt, size M\",\n    \"parentId\": { \"value\": \"7189\", \"valueId\": \"3895\" },\n    \"type\": 4,\n    \"active\": true,\n    \"available\": true,\n    \"code\": null,\n    \"xmlId\": \"7191\",\n    \"sort\": 500,\n    \"measure\": 9,\n    \"vatId\": null,\n    \"vatIncluded\": false,\n    \"purchasingPrice\": 450,\n    \"purchasingCurrency\": \"USD\",\n    \"quantity\": null,\n    \"quantityReserved\": null,\n    \"quantityTrace\": true,\n    \"canBuyZero\": true,\n    \"weight\": null,\n    \"width\": null,\n    \"height\": null,\n    \"length\": null,\n    \"previewText\": null,\n    \"previewTextType\": \"text\",\n    \"detailPicture\": null,\n    \"property907\": null,\n    \"property909\": null,\n    \"createdBy\": 1295,\n    \"dateCreate\": \"2026-10-08T18:41:55.000Z\",\n    \"timestampX\": \"2026-10-08T18:41:55.000Z\"\n  }\n}\n```\n\n## Error response example\n\n404 — no offer with this `id` exists:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"ENTITY_NOT_FOUND\",\n    \"message\": \"offer 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 (`offer does not exist.`) |\n| 404 | `ENTITY_NOT_FOUND` | A record with the specified `id` exists, but it is not an offer — for example, a regular or parent product (`catalogOffer \u003Cid> not found`) |\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\nFull list of common API errors — [Errors](\u002Fdocs\u002Ferrors).\n\n## Known specifics\n\n**Free offer.** An offer created without a parent product is returned with `parentId: null` and `type: 5`. An offer linked to a parent product has `type` `4`, and the parent product ID is returned as a string in `parentId.value`.\n\n## See also\n\n- [Create an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fcreate)\n- [Update an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fupdate)\n- [Delete an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fdelete)\n- [List offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Offer fields](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Ffields)\n- [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Catalog product properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","\n## Update an offer\n\n`PATCH \u002Fv1\u002Fcatalog-offers\u002F:id`\n\nUpdates an existing product offer. Fields are passed flat at the JSON root. Pass only the ones you change — the rest keep their current values.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `id` (path) | number | yes | Offer identifier. List: [`GET \u002Fv1\u002Fcatalog-offers`](.\u002Flist.md) |\n\n## Request fields (body)\n\nThe main updatable fields are `name`, `active`, `purchasingPrice`, and `quantity`.\n\n| Field | Type | Required | Description |\n|------|-----|:-----:|---------|\n| `name` | string | no | Offer name |\n| `active` | boolean | no | Whether the offer is active |\n| `iblockSectionId` | number | no | Catalog section ID. List: `GET \u002Fv1\u002Fcatalog-sections` |\n| `measure` | number | no | Unit of measure ID. List: `GET \u002Fv1\u002Fcatalog-measures` |\n| `weight` | number | no | Weight of one product unit |\n| `vatIncluded` | boolean | no | VAT included in the price |\n| `canBuyZero` | boolean | no | Allow purchase when stock is zero |\n| `quantityTrace` | boolean | no | Enable quantity tracking |\n| `subscribe` | boolean | no | Allow subscription to the product |\n| `barcodeMulti` | boolean | no | Separate barcodes for product units |\n| `withoutOrder` | boolean | no | Available for ordering without stock on hand |\n| `purchasingPrice` | number | no | Purchase price |\n| `purchasingCurrency` | string | no | Purchase price currency. List: `GET \u002Fv1\u002Fcurrencies` |\n| `quantity` | number | no | Stock on hand |\n| `quantityReserved` | number | no | Reserved quantity |\n| `code` | string | no | Symbolic code |\n| `xmlId` | string | no | External code |\n| `sort` | number | no | Sort order |\n| `vatId` | number | no | VAT rate ID. List: `GET \u002Fv1\u002Fcatalog-vat-rates` |\n| `height` | number | no | Height |\n| `length` | number | no | Length |\n| `width` | number | no | Width |\n| `dateActiveFrom` | datetime | no | Activity start |\n| `dateActiveTo` | datetime | no | Activity end |\n| `previewText` | string | no | Preview text |\n| `detailText` | string | no | Detailed description |\n| `previewTextType` | string | no | Preview text format: `text` or `html` |\n| `detailTextType` | string | no | Detailed description format: `text` or `html` |\n| `previewPicture` | object | no | Preview image |\n| `detailPicture` | object | no | Detailed image |\n| `iblockSection` | object | no | Array of IDs of the catalog sections the offer belongs to. List: `GET \u002Fv1\u002Fcatalog-sections` |\n| `recurSchemeLength` | number | no | Payment period length. Only for on-premise Bitrix24 when selling content |\n| `recurSchemeType` | string | no | Payment period unit: `H`, `D`, `W`, `M`, `Q`, `S`, `Y`. Only for on-premise Bitrix24 when selling content |\n| `trialPriceId` | number | no | ID of the trial payment product. Only for on-premise Bitrix24 when selling content |\n| `propertyNNN` | object\u002Farray | no | Catalog property value as an object `{ \"value\": … }`, where `NNN` is the property ID. Catalog properties are listed in [`GET \u002Fv1\u002Fcatalog-offers\u002Ffields?iblockId=\u003Cid>`](.\u002Ffields.md) |\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 `parentId`, `iblockId` and `type` fields are not accepted on update: the link to the parent product and the catalog are set at creation, and Bitrix24 computes the type. Fields with `readonly: true` in [Offer fields](.\u002Ffields.md), for example `available`, are filled by the system. A body with any of these fields 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-offers\u002F7191\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"purchasingPrice\": 470\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X PATCH \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191\" \\\n  -H \"X-Api-Key: YOUR_APP_KEY\" \\\n  -H \"Authorization: Bearer USER_SESSION_TOKEN\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"purchasingPrice\": 470\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191', {\n  method: 'PATCH',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    purchasingPrice: 470,\n  }),\n})\n\nconst { success, data } = await res.json()\nconsole.log('Purchase price:', data.purchasingPrice)\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191', {\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    purchasingPrice: 470,\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 offer. The field set is the same as in [`GET \u002Fv1\u002Fcatalog-offers\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\": 7191,\n    \"iblockId\": 27,\n    \"name\": \"Basic T-shirt, size M\",\n    \"parentId\": { \"value\": \"7189\", \"valueId\": \"3895\" },\n    \"type\": 4,\n    \"active\": true,\n    \"available\": true,\n    \"measure\": 9,\n    \"purchasingPrice\": 470,\n    \"purchasingCurrency\": \"USD\",\n    \"quantity\": null,\n    \"dateCreate\": \"2026-10-08T18:41:55.000Z\",\n    \"timestampX\": \"2026-10-08T18:41:59.000Z\"\n  }\n}\n```\n\n## Error response example\n\n404 — no offer with the specified `id` exists:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"ENTITY_NOT_FOUND\",\n    \"message\": \"offer 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 (`offer does not exist.`) |\n| 422 | `BITRIX_ERROR` | The `id` belongs to a regular product, not an offer (`productType is not allowed for this catalog`) |\n| 400 | `EMPTY_UPDATE_BODY` | The request body is empty — pass at least one field |\n| 400 | `NO_RECOGNIZED_UPDATE_FIELDS` | The body has 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: `parentId`, `iblockId`, `type` or a read-only field, for example `available` |\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 is passed as an object.** A `PATCH` with a scalar property value — `\"property891\": \"B-200\"` — returns `200` but clears the value instead of writing it. String and list properties behave this way. Pass `{ \"value\": … }`, and for a list property pass the option ID as a string: `{ \"value\": \"1601\" }`. Option IDs are returned by [`GET \u002Fv1\u002Fcatalog-product-property-enums`](\u002Fdocs\u002Fentities\u002Fcatalog-product-property-enums\u002Flist) with `filter[propertyId]`. When you create an offer, a property also accepts a plain value.\n\n**An offer cannot be moved to another parent product.** `parentId` is set only at creation. To link the offer to another parent product, recreate it with the required `parentId` and delete the old one.\n\n## See also\n\n- [Create an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fcreate)\n- [List offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Get an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fget)\n- [Delete an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fdelete)\n- [Offer fields](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Ffields)\n- [Catalog prices](\u002Fdocs\u002Fentities\u002Fcatalog-prices)\n- [Batch](\u002Fdocs\u002Fbatch)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","\n## Delete an offer\n\n`DELETE \u002Fv1\u002Fcatalog-offers\u002F:id`\n\nDeletes a product offer by identifier. A deleted offer 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 | Offer identifier. List: [`GET \u002Fv1\u002Fcatalog-offers`](.\u002Flist.md) |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X DELETE \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191\" \\\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-offers\u002F7191\" \\\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-offers\u002F7191', {\n  method: 'DELETE',\n  headers: {\n    'X-Api-Key': 'YOUR_API_KEY',\n  },\n})\n\nif (res.status === 204) {\n  console.log('Offer deleted')\n}\n```\n\n### JavaScript — OAuth application\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002F7191', {\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('Offer deleted')\n}\n```\n\n## Response\n\nOn successful deletion, an HTTP status `204 No Content` is returned with an empty body — success is checked by the status.\n\n## Response example\n\n```http\nHTTP\u002F1.1 204 No Content\n```\n\n## Error response example\n\n404 — no offer with the specified `id` exists:\n\n```json\n{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"ENTITY_NOT_FOUND\",\n    \"message\": \"offer 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, including on a repeated deletion (`offer does not exist.`) |\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**The method does not check that the `id` belongs to an offer.** With the `id` of a regular product, the call returns `204` and deletes that product. Check the record before deleting: [`GET \u002Fv1\u002Fcatalog-offers\u002F:id`](.\u002Fget.md) returns `404 ENTITY_NOT_FOUND` for an `id` that does not belong to an offer.\n\n**Offers are deleted together with their parent product.** After [`DELETE \u002Fv1\u002Fcatalog-skus\u002F:id`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Fdelete), the offers of that parent product return `404 ENTITY_NOT_FOUND`; there is no need to delete them separately.\n\n## See also\n\n- [Create an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fcreate)\n- [List offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Get an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fget)\n- [Update an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fupdate)\n- [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Batch](\u002Fdocs\u002Fbatch)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n","\n## Search offers\n\n`POST \u002Fv1\u002Fcatalog-offers\u002Fsearch`\n\nSearch product offers using the conditions passed in the request body.\n\nUnlike [`GET \u002Fv1\u002Fcatalog-offers`](.\u002Flist.md), the `filter`, `select`, `sort`, `limit`, and `offset` parameters are passed in the JSON body rather than in the query string — conditions across several fields are expressed 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 date fields `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` are not accepted in a filter: the Bitrix24 method does not apply date filters and would return every offer in the catalog. A request with such a filter is rejected with `400 UNKNOWN_FILTER_FIELD`.\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-offers\u002Ffields`](.\u002Ffields.md). The `iblockId` key is required — the offer catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs); this catalog has `productIblockId` filled in. Offers of one parent product are selected by `parentId` with an ID from [`GET \u002Fv1\u002Fcatalog-skus`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Flist). `type` accepts one exact value: `4` or `5`.\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering). Example: `{ \"iblockId\": 27, \"parentId\": 7189 }` |\n| `select` | string[] | no | — | Field selection: `[\"iblockId\", \"id\", \"name\", \"parentId\"]`. If the parameter is passed, it must include `iblockId` and `id`. Without `select`, the response carries a reduced set of fields: the symbolic code, dimensions, texts, images, and catalog properties are returned only when listed explicitly — [Offer 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-offers\u002Fsearch\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"iblockId\": 27, \"parentId\": 7189 },\n    \"limit\": 10\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\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\": 27, \"parentId\": 7189 },\n    \"limit\": 10\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\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: 27, parentId: 7189 },\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-offers\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: 27, parentId: 7189 },\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 offers. For the fields of each item, see [Offer fields](.\u002Ffields.md) |\n| `meta.total` | number | Number of records that 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` length equal to `limit` does not rule out the last page.\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": [\n    {\n      \"active\": true,\n      \"available\": true,\n      \"barcodeMulti\": false,\n      \"bundle\": false,\n      \"canBuyZero\": true,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T18:41:55.000Z\",\n      \"iblockId\": 27,\n      \"iblockSectionId\": null,\n      \"id\": 7191,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Basic T-shirt, size M\",\n      \"parentId\": { \"value\": \"7189\", \"valueId\": \"3895\" },\n      \"purchasingCurrency\": \"USD\",\n      \"purchasingPrice\": 450,\n      \"quantity\": null,\n      \"quantityTrace\": true,\n      \"subscribe\": true,\n      \"timestampX\": \"2026-10-08T18:41:55.000Z\",\n      \"type\": 4,\n      \"vatIncluded\": false,\n      \"weight\": null,\n      \"withoutOrder\": false\n    },\n    {\n      \"active\": true,\n      \"available\": true,\n      \"barcodeMulti\": false,\n      \"bundle\": false,\n      \"canBuyZero\": true,\n      \"createdBy\": 1295,\n      \"dateActiveFrom\": null,\n      \"dateActiveTo\": null,\n      \"dateCreate\": \"2026-10-08T18:41:56.000Z\",\n      \"iblockId\": 27,\n      \"iblockSectionId\": null,\n      \"id\": 7193,\n      \"measure\": 9,\n      \"modifiedBy\": 1295,\n      \"name\": \"Basic T-shirt, size L\",\n      \"parentId\": { \"value\": \"7189\", \"valueId\": \"3897\" },\n      \"purchasingCurrency\": \"USD\",\n      \"purchasingPrice\": 490,\n      \"quantity\": 10,\n      \"quantityTrace\": true,\n      \"subscribe\": true,\n      \"timestampX\": \"2026-10-08T18:41:56.000Z\",\n      \"type\": 4,\n      \"vatIncluded\": false,\n      \"weight\": null,\n      \"withoutOrder\": false\n    }\n  ],\n  \"meta\": {\n    \"total\": 2,\n    \"hasMore\": false,\n    \"durationMs\": 474\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-offers\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 | `UNSUPPORTED_FILTER` | `filter.type` carries a value other than `4` and `5`, an array, or an operator |\n| 400 | `UNKNOWN_FILTER_FIELD` | Filtering by a field the offer does not have, or by 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 offer does not have. The message contains the list of available fields |\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| 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 offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Create an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fcreate)\n- [Get an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fget)\n- [Offer fields](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Ffields)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","\n## Offer fields\n\n`GET \u002Fv1\u002Fcatalog-offers\u002Ffields`\n\nReturns the offer 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\nWith the `iblockId` parameter, the reference also includes the fields of a specific offer catalog — its `propertyNNN` properties.\n\n## Parameters\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `iblockId` (query) | number | no | ID of the information block backing the offer catalog: `catalogs[].iblockId` from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs), not the catalog record's `catalogs[].id`; this catalog has `productIblockId` filled in. With it, the response contains the catalog properties `propertyNNN` and the service fields `negativeAmountTrace`, `priceType`. Without the parameter, with a product catalog's `iblockId`, or with a nonexistent value, the response carries the base set of 44 fields and the `fields_partial` warning in `meta.warnings` |\n\nThe example's `iblockId=27` is the offer catalog's `catalogs[].iblockId` value. If the catalog record has a different `id`, use its `iblockId`.\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002Ffields?iblockId=27\" \\\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-offers\u002Ffields?iblockId=27\" \\\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-offers\u002Ffields?iblockId=27', {\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-offers\u002Ffields?iblockId=27', {\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); numeric functions accept the fields of type `number`. `data.batch` lists the operations available in a [batch request](\u002Fdocs\u002Fbatch).\n\n| Field | Type | RO | Description |\n|------|-----|:--:|---------|\n| `id` | number | yes | Offer identifier |\n| `name` | string | no | Offer name. Required on create |\n| `active` | boolean | no | Whether the offer is active |\n| `iblockId` | number | no¹ | Offer catalog ID. List: [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs). Required on create, set on create only |\n| `parentId` | object \\| null | no² | Link to the parent product: `value` — the parent product ID as a string, `valueId` — the service ID of the link value, read-only. On create, pass `{ \"value\": \"\u003CID>\" }`. `null` — a free offer. Parent product IDs: [`GET \u002Fv1\u002Fcatalog-skus`](\u002Fdocs\u002Fentities\u002Fcatalog-skus\u002Flist) |\n| `type` | number | yes | Record type, computed by Bitrix24: `4` — the offer is linked to a parent product, `5` — a free offer. The filter accepts one exact value |\n| `iblockSectionId` | number \\| null | no | Catalog section ID. `null` — the offer is not linked to a section. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections) |\n| `purchasingPrice` | number \\| null | no | Purchase price. `null` if not set |\n| `purchasingCurrency` | string \\| null | no | Purchase price currency, for example `USD`. `null` if the purchase price is not set. List: [`GET \u002Fv1\u002Fcurrencies`](\u002Fdocs\u002Fentities\u002Fcurrencies) |\n| `quantity` | number \\| null | no | Stock balance. `null` if not set |\n| `weight` | number \\| null | no | Weight of a product unit. `null` if not specified |\n| `measure` | number | no | Unit of measure ID. List: [`GET \u002Fv1\u002Fcatalog-measures`](\u002Fdocs\u002Fentities\u002Fcatalog-measures) |\n| `available` | boolean | yes | Whether the offer is available for purchase. Computed by Bitrix24 |\n| `vatIncluded` | boolean | no | VAT included in the price |\n| `bundle` | boolean | yes | Whether the offer is a bundle. Computed by Bitrix24 |\n| `canBuyZero` | boolean | no | Allow purchase when stock is zero |\n| `quantityTrace` | boolean | no | Quantity tracking enabled |\n| `subscribe` | boolean | no | Allow subscription to the product |\n| `barcodeMulti` | boolean | no | Allow separate barcodes for product units |\n| `withoutOrder` | boolean | no | Available for ordering without stock on hand |\n| `dateActiveFrom` | datetime | no | Activity start date. Not accepted in a filter |\n| `dateActiveTo` | datetime | no | Activity end date. Not accepted in a filter |\n| `createdBy` | number | yes | ID of the user who created the offer. List: [`GET \u002Fv1\u002Fusers`](\u002Fdocs\u002Fentities\u002Fusers) |\n| `modifiedBy` | number | yes | ID of the user who last modified the offer. List: [`GET \u002Fv1\u002Fusers`](\u002Fdocs\u002Fentities\u002Fusers) |\n| `dateCreate` | datetime | yes | Creation date. Not accepted in a filter |\n| `timestampX` | datetime | yes | Last modification date. Not accepted in a filter |\n| `code` | string \\| null | no | Symbolic code. `null` if not set |\n| `xmlId` | string | no | External identifier |\n| `sort` | number | no | Sort order |\n| `vatId` | number | no | VAT rate ID. List: [`GET \u002Fv1\u002Fcatalog-vat-rates`](\u002Fdocs\u002Fentities\u002Fcatalog-vat-rates) |\n| `previewText` | string | no | Preview text |\n| `detailText` | string | no | Detailed description |\n| `previewTextType` | string | no | Preview text format: `text` or `html` |\n| `detailTextType` | string | no | Detailed description format: `text` or `html` |\n| `previewPicture` | object | no | Preview image |\n| `detailPicture` | object | no | Detail image |\n| `iblockSection` | object | no | Array of catalog section IDs to write on create and update. The primary section is read from `iblockSectionId`. List: [`GET \u002Fv1\u002Fcatalog-sections`](\u002Fdocs\u002Fentities\u002Fcatalog-sections) |\n| `width` | number | no | Width |\n| `height` | number | no | Height |\n| `length` | number | no | Length |\n| `quantityReserved` | number \\| null | no | Reserved quantity. `null` if nothing is reserved |\n| `recurSchemeLength` | number | no | Payment period length. Available only in on-premise Bitrix24 for content sales |\n| `recurSchemeType` | string | no | Payment period time unit: `H` — hour, `D` — day, `W` — week, `M` — month, `Q` — quarter, `S` — half-year, `Y` — year. Available only in on-premise Bitrix24 for content sales |\n| `trialPriceId` | number | no | ID of the product used for a trial payment. Available only in on-premise Bitrix24 for content sales |\n| `negativeAmountTrace` | char | yes | Catalog service field. Returned only with `iblockId` |\n| `priceType` | char | no | Catalog service field. Returned only with `iblockId` |\n| `propertyNNN` | productproperty | no | Catalog property, where `NNN` is the property `id` from [`GET \u002Fv1\u002Fcatalog-product-properties`](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties). Returned only with `iblockId`; multiple-value properties come with `multiple: true` |\n\n¹ `iblockId` is writable on create only — in the reference it comes back with `readonly: false` and `createOnly: true`.\n\n² `parentId` is writable on create only — in the reference it comes back with `readonly: false` and `readonlyOnUpdate: true`.\n\nRequest headers do not switch the language of labels and descriptions. A field with a fixed set of values — `previewTextType` and `detailTextType` — also carries an `enum` array: every element holds a `value` to pass in the request and a `label` for display.\n\n| Field | Type | Description |\n|------|-----|---------|\n| `success` | boolean | Always `true` on success |\n| `data.fields.\u003Cname>.type` | string | Field type: `number`, `string`, `boolean`, `datetime`, `object`. Catalog fields use `char` and `productproperty` |\n| `data.fields.\u003Cname>.label` | string | Short field label. For catalog fields the label matches the field name |\n| `data.fields.\u003Cname>.description` | string | Extended field description: what it is for, where to get the list of allowed values, write-time behavior. The key is present on fields that have something to add to the label |\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>.required` | boolean | `true` — the field is required on create. Present on `name` and `iblockId` |\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>.readonlyOnUpdate` | boolean | `true` — the field is accepted on create; on `PATCH` it is rejected with `READONLY_FIELD`. Present on `parentId` |\n| `data.fields.\u003Cname>.properties` | object | Nested keys of an object field with their types. On `parentId` — `value` and `valueId`, the latter with `readonly: true` |\n| `data.fields.\u003Cname>.writeOnly` | boolean | `true` — the field is accepted on write and is not returned in responses. Present on `iblockSection` |\n| `data.fields.\u003Cname>.notReturned` | boolean | `true` — the field name is not supported in `select`: a list request with it returns an `UNKNOWN_SELECT_FIELD` warning. 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 catalog property accepts several values. The key is present only on such properties |\n| `data.aggregatable` | string[] | Fields for `groupBy` in [aggregation](.\u002Faggregate.md): `purchasingPrice`, `quantity`, `iblockSectionId` |\n| `data.batch` | string[] | Offer operations available in a [batch request](\u002Fdocs\u002Fbatch): `create`, `update`, `delete` |\n| `meta.warnings` | array | Warnings. An element with `code: fields_partial` arrives when the response is built without catalog fields — without `iblockId`, or with an `iblockId` that does not belong to an offer catalog |\n\n## Response example\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"fields\": {\n      \"id\": {\n        \"type\": \"number\",\n        \"readonly\": true,\n        \"label\": \"Product identifier\",\n        \"description\": \"Unique identifier of the catalog product.\"\n      },\n      \"iblockId\": {\n        \"type\": \"number\",\n        \"readonly\": false,\n        \"createOnly\": true,\n        \"required\": true,\n        \"label\": \"Catalog ID\",\n        \"description\": \"Catalog the product belongs to. Available values: GET \u002Fv1\u002Fcatalogs. Set on create only — changing it via PATCH is rejected, a product cannot be moved between catalogs.\"\n      },\n      \"parentId\": {\n        \"type\": \"object\",\n        \"readonly\": false,\n        \"readonlyOnUpdate\": true,\n        \"nullable\": true,\n        \"label\": \"Parent product ID\",\n        \"description\": \"Link to a catalog SKU or product. On create pass {\\\"value\\\":\\\"\u003Cparent id>\\\"}; omit for a free offer. Bitrix24 returns value and valueId. Updates to this field are rejected because Bitrix24 silently ignores them.\",\n        \"properties\": {\n          \"value\": { \"type\": \"string\" },\n          \"valueId\": { \"type\": \"string\", \"readonly\": true }\n        }\n      },\n      \"type\": {\n        \"type\": \"number\",\n        \"readonly\": true,\n        \"label\": \"Bitrix24 product type\",\n        \"description\": \"Computed by Bitrix24. A free offer has a different type from an offer linked to a parent. Filtering accepts only exact type 4 or 5.\"\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 product 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      \"property909\": {\n        \"type\": \"productproperty\",\n        \"readonly\": false,\n        \"multiple\": true,\n        \"label\": \"property909\"\n      }\n    },\n    \"aggregatable\": [\n      \"purchasingPrice\",\n      \"quantity\",\n      \"iblockSectionId\"\n    ],\n    \"batch\": [\n      \"create\",\n      \"update\",\n      \"delete\"\n    ]\n  }\n}\n```\n\nThe example is trimmed to seven fields — one each for `readonly`, `createOnly` with `required`, `readonlyOnUpdate` with `properties`, the computed `type`, the `writeOnly` + `notReturned` pair, an `enum` dictionary and a multiple-value catalog property. With `iblockId=27`, the test Bitrix24 account returns 55 fields.\n\nWithout `iblockId`, the response contains a warning:\n\n```json\n{\n  \"success\": true,\n  \"data\": { \"fields\": { \u002F* 44 fields *\u002F }, \"aggregatable\": [ \u002F* ... *\u002F ], \"batch\": [ \u002F* ... *\u002F ] },\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\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**A retry does not clear `fields_partial` without `iblockId`.** The warning suggests retrying the request, but without the offer catalog's `iblockId` a retry returns the same base set. The forty-four base fields in this response are complete; only the catalog properties and service fields are missing — a request with `?iblockId=` returns them.\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 dimensions `width`, `height`, `length`, the texts, the pictures, `quantityReserved`, `recurSchemeLength`, `recurSchemeType`, `trialPriceId` and the `propertyNNN` properties are not returned in the [`GET \u002Fv1\u002Fcatalog-offers`](.\u002Flist.md) response without an explicit `?select=`. List the names you need in `select` to get them in the list. The single-offer response, [`GET \u002Fv1\u002Fcatalog-offers\u002F:id`](.\u002Fget.md), always returns them.\n\n**Labels call the record a product.** In the reference's `label` and `description`, an offer is called a product — the labels match [Catalog product fields](\u002Fdocs\u002Fentities\u002Fcatalog-products\u002Ffields). The `type` and `parentId` fields exist only on offers.\n\n## See also\n\n- [List offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Create an offer](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fcreate)\n- [Search offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fsearch)\n- [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Catalog product properties](\u002Fdocs\u002Fentities\u002Fcatalog-product-properties)\n- [API reference](\u002Fdocs\u002Fapi-reference)\n","\n## Aggregate offers\n\n`POST \u002Fv1\u002Fcatalog-offers\u002Faggregate`\n\nCounts product offers and aggregates their numeric fields — sum, average, minimum and maximum — with filtering and grouping.\n\n**Fields available for aggregation:**\n\n- `purchasingPrice` — purchase price, suitable for numeric aggregation\n- `quantity` — stock balance, suitable for numeric aggregation\n- `iblockSectionId` — catalog section, for `groupBy`\n\n## Request fields (body)\n\n| Parameter | Type | Required | Description |\n|----------|-----|:-----:|---------|\n| `filter` | object | yes | Filtering by the fields of [`GET \u002Fv1\u002Fcatalog-offers\u002Ffields`](.\u002Ffields.md). The `iblockId` key is required — the offer catalog ID from [`GET \u002Fv1\u002Fcatalogs`](\u002Fdocs\u002Fentities\u002Fcatalogs). `parentId` selects the offers of one parent product; `type` accepts one exact value: `4` or `5`. The date fields `dateCreate`, `timestampX`, `dateActiveFrom`, and `dateActiveTo` are not accepted: the Bitrix24 method does not apply date filters, and the request is rejected with `400 UNKNOWN_FILTER_FIELD`.\u003Cbr>[Filtering syntax](\u002Fdocs\u002Ffiltering) |\n| `aggregate` | array | no | Aggregations: `[{ \"field\": \"purchasingPrice\", \"function\": \"sum\" }]`. 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). Values — from the list above |\n\n## Examples\n\n### curl — personal key\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\u002Faggregate\" \\\n  -H \"X-Api-Key: YOUR_API_KEY\" \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\n    \"filter\": { \"iblockId\": 27, \"parentId\": 7189 },\n    \"aggregate\": [\n      { \"field\": \"purchasingPrice\", \"function\": \"sum\" },\n      { \"field\": \"purchasingPrice\", \"function\": \"avg\" }\n    ],\n    \"groupBy\": \"iblockSectionId\"\n  }'\n```\n\n### curl — OAuth application\n\n```bash\ncurl -X POST \"https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\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\": 27, \"parentId\": 7189 },\n    \"aggregate\": [\n      { \"field\": \"purchasingPrice\", \"function\": \"sum\" },\n      { \"field\": \"purchasingPrice\", \"function\": \"avg\" }\n    ],\n    \"groupBy\": \"iblockSectionId\"\n  }'\n```\n\n### JavaScript — personal key\n\n```javascript\nconst res = await fetch('https:\u002F\u002Fvibecode.bitrix24.com\u002Fv1\u002Fcatalog-offers\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: 27, parentId: 7189 },\n    aggregate: [\n      { field: 'purchasingPrice', function: 'sum' },\n      { field: 'purchasingPrice', function: 'avg' },\n    ],\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-offers\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: 27, parentId: 7189 },\n    aggregate: [\n      { field: 'purchasingPrice', function: 'sum' },\n      { field: 'purchasingPrice', function: 'avg' },\n    ],\n    groupBy: 'iblockSectionId',\n  }),\n})\n\nconst { success, data } = await res.json()\n```\n\nTo group by several fields, pass an array: `\"groupBy\": [\"iblockSectionId\", \"quantity\"]`, up to 5 fields.\n\n## Other scenarios\n\nThe blocks below are request bodies.\n\nNumber of offers in a catalog — a body without `aggregate`, the fastest request, with no records fetched. The `iblockId` filter is required here too:\n\n```json\n{ \"filter\": { \"iblockId\": 27 } }\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 offers matching the filter |\n| `data.aggregates` | object | Aggregation results: `{ \"purchasingPrice\": { \"sum\": 0, \"avg\": 0 } }`. 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 a numeric function, 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      \"purchasingPrice\": { \"sum\": 940, \"avg\": 470 }\n    },\n    \"groups\": [\n      {\n        \"iblockSectionId\": null,\n        \"count\": 2,\n        \"aggregates\": { \"purchasingPrice\": { \"sum\": 940, \"avg\": 470 } }\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 aggregation:\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: purchasingPrice, quantity, iblockSectionId.\"\n  }\n}\n```\n\n## Errors\n\n| HTTP | Code | Description |\n|------|-----|---------|\n| 400 | `MISSING_REQUIRED_FILTER` | The required filter `filter.iblockId` was not passed — checked before the Bitrix24 call; the message contains an example body |\n| 400 | `UNSUPPORTED_FILTER` | `filter.type` received a value other than `4` and `5`, an array, or an operator |\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| 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. Numeric functions are not.** `count` is computed in a single call regardless of the size of the result set. The `sum`, `avg`, `min`, `max` functions and `groupBy` 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 aggregates and 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 offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Flist)\n- [Search offers](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Fsearch)\n- [Offer fields](\u002Fdocs\u002Fentities\u002Fcatalog-offers\u002Ffields)\n- [Catalog SKUs](\u002Fdocs\u002Fentities\u002Fcatalog-skus)\n- [Filtering syntax](\u002Fdocs\u002Ffiltering)\n- [Limits and optimization](\u002Fdocs\u002Foptimization)\n",1794739328537]