
## Добавить позицию в корзину

`POST /v1/basket-items`

Добавляет товарную позицию в существующий заказ. Позиция ссылается на товар каталога через `productId` либо создаётся виртуальной при `productId: 0`. Тело запроса плоское — без обёртки `fields`.

## Поля запроса (тело)

| Параметр | Тип | Обяз. | Описание |
|----------|-----|:-----:|---------|
| `orderId` | number | да | Идентификатор заказа. Источник: [`GET /v1/orders`](../orders/list.md) |
| `productId` | number | да | Идентификатор товара в каталоге. Источник: [`GET /v1/catalog-products`](/docs/entities/catalog-products). Значение `0` создаёт виртуальную позицию без привязки к каталогу |
| `currency` | string | да | Валюта позиции. Список: [`GET /v1/currencies`](/docs/entities/currencies) |
| `quantity` | number | да | Количество |
| `name` | string | нет | Название позиции. При `productId > 0` подставляется из карточки товара — переданное значение на создании не сохраняется. При `productId: 0` сохраняется как передано |
| `price` | number | нет | Цена за единицу. Для виртуальной позиции задаётся вручную. Передача `price` включает `customPrice` |
| `basePrice` | number | нет | Базовая цена до скидки. По умолчанию равна `price` |
| `discountPrice` | number | нет | Размер скидки за единицу. По умолчанию `0` |
| `customPrice` | boolean | нет | Защита цены от автопересчёта при изменении товара в каталоге. При передаче `price` выставляется в `true`. По умолчанию `false` |
| `vatRate` | number | нет | Ставка НДС в долях единицы. `0.20` = 20%, `0.10` = 10%, `0` — без НДС |
| `vatIncluded` | boolean | нет | Включён ли НДС в цену. По умолчанию `true` |
| `weight` | number | нет | Вес единицы в граммах |
| `measureCode` | number | нет | Код единицы измерения. `796` — шт, `163` — г, `006` — м |
| `productXmlId` | string | нет | Внешний идентификатор товара. Для виртуальной позиции задаётся вручную |
| `catalogXmlId` | string | нет | Внешний идентификатор каталога. Для виртуальной позиции задаётся вручную |
| `xmlId` | string | нет | Внешний идентификатор позиции |

## Примеры

Примеры ниже создают виртуальную позицию `productId: 0` — для неё `name` сохраняется как передано.

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

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/basket-items" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": 1,
    "productId": 0,
    "currency": "RUB",
    "quantity": 1,
    "name": "Подарочная упаковка"
  }'
```

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

```bash
curl -X POST "https://vibecode.bitrix24.tech/v1/basket-items" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": 1,
    "productId": 0,
    "currency": "RUB",
    "quantity": 1,
    "name": "Подарочная упаковка"
  }'
```

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

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/basket-items', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    orderId: 1,
    productId: 0,
    currency: 'RUB',
    quantity: 1,
    name: 'Подарочная упаковка',
  }),
})

const { success, data } = await res.json()
console.log('Basket item ID:', data.id)
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/basket-items', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    orderId: 1,
    productId: 0,
    currency: 'RUB',
    quantity: 1,
    name: 'Подарочная упаковка',
  }),
})

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

Каталожная позиция создаётся передачей `productId` товара — `name`, `price` и единица измерения заполнятся из его карточки:

```json
{
  "orderId": 1,
  "productId": 119,
  "currency": "RUB",
  "quantity": 2
}
```

## Поля ответа

Возвращается полный объект созданной позиции.

| Поле | Тип | Описание |
|------|-----|---------|
| `id` | number | Идентификатор созданной позиции |
| `name` | string | Название. Для каталожного товара — из карточки, для виртуальной позиции — как передано |
| `price` | number | Цена за единицу |
| `customPrice` | boolean | `true`, если цена задана вручную через `price` |
| `productXmlId` | string \| null | Внешний идентификатор товара. `null` у виртуальной позиции без переданного значения |
| `catalogXmlId` | string \| null | Внешний идентификатор каталога. `null` у виртуальной позиции без переданного значения |
| `dateInsert` | datetime | Дата создания |

Полный набор полей — [`GET /v1/basket-items/fields`](./fields.md).

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

```json
{
  "success": true,
  "data": {
    "id": 1021,
    "orderId": 1,
    "productId": 0,
    "name": "Подарочная упаковка",
    "price": 0,
    "basePrice": 0,
    "discountPrice": 0,
    "customPrice": false,
    "currency": "RUB",
    "quantity": 1,
    "sort": 100,
    "weight": null,
    "dimensions": null,
    "measureCode": null,
    "measureName": null,
    "canBuy": true,
    "vatRate": null,
    "vatIncluded": true,
    "xmlId": "bx_6a3e475a2d55c",
    "productXmlId": null,
    "catalogXmlId": null,
    "dateInsert": "2026-06-26T08:33:14.000Z",
    "dateUpdate": "2026-06-26T08:33:14.000Z"
  }
}
```

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

422 — не переданы обязательные поля:

```json
{
  "success": false,
  "error": {
    "code": "BITRIX_ERROR",
    "message": "Required fields: orderId, productId, currency, quantity"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 422 | `BITRIX_ERROR` | Не переданы обязательные поля — сообщение содержит их список |
| 422 | `BITRIX_ERROR` | Несуществующий `orderId` или `productId` — позиция не сохранена |
| 403 | `SCOPE_DENIED` | API-ключ не имеет скоупа `sale` |
| 401 | `TOKEN_MISSING` | API-ключ не имеет настроенных токенов |

Полный список общих ошибок API — [Ошибки](/docs/errors).

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

**Имя каталожного товара берётся из карточки.** При `productId > 0` поля `name`, `productXmlId`, `catalogXmlId`, `measureCode`, `measureName` заполняются из карточки товара. Переданное в запросе `name` на создании не сохраняется. Чтобы задать каталожной позиции произвольное имя, обновите её после создания через [`PATCH /v1/basket-items/:id`](./update.md).

**Виртуальная позиция.** При `productId: 0` позиция не привязана к каталогу. Поля `name`, `price`, `productXmlId`, `catalogXmlId` задаются вручную в запросе и сохраняются как переданы.

**Ручная цена включает `customPrice`.** Если передать `price`, в ответе `customPrice` становится `true` — цена позиции не пересчитывается при изменении цены товара в каталоге. Без `price` остаётся `false`.

**Несколько позиций для одного товара.** Можно добавить одну и ту же `productId` в заказ несколькими позициями — Битрикс24 не объединяет их. Чтобы увеличить количество существующей позиции, используйте [`PATCH /v1/basket-items/:id`](./update.md) с `quantity`.

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

- [Список позиций](./list.md)
- [Получить позицию](./get.md)
- [Обновить позицию](./update.md)
- [Поля позиции](./fields.md)
- [Заказ](../orders/get.md)
- [Товары каталога](/docs/entities/catalog-products)
- [Batch](/docs/batch)
