For AI agents: markdown of this page — /docs-content-en/entities/leads/products-set.md documentation index — /llms.txt

Set lead products

PUT /v1/leads/:id/products

Sets the lead product rows. Replaces the current list as a whole — pass all the rows you need.

Field names are validated. A field name that is not in the writable set is not dropped silently: the request is rejected with 400 INVALID_PARAMS and the error text lists the writable names. Read-only fields that appear in product-row responses — priceAccount, ownerId, storeId and others — are accepted and ignored, so an object read via GET can be sent back as is.

Row identifiers do not carry over. A row whose fields are unchanged keeps its id. A modified one comes back with a new id even when the previous identifier is sent in the body. To edit a row and keep its id, use PATCH /v1/leads/:id/products/:rowId.

Parameters

Parameter Type Required Description
Parameterid (path) Typenumber Requiredyes DescriptionLead ID. List: GET /v1/leads

Request fields (body)

Field Type Required Description
Fielditems Typearray Requiredyes DescriptionArray of product rows. An empty array deletes every row of the lead
Fielditems[].productId Typenumber Requiredno DescriptionCatalog product ID. Catalog: GET /v1/products. With neither productId nor productName, the row is created with no product and an empty name
Fielditems[].productName Typestring Requiredno DescriptionRow name — for a free-text row with no catalog product
Fielditems[].price Typenumber Requiredno DescriptionUnit price. Without it the row is created with a zero price
Fielditems[].quantity Typenumber Requiredno DescriptionQuantity. Without it the row is created with a quantity of 1
Fielditems[].discountTypeId Typenumber Requiredno DescriptionHow the discount is set: 1 — as an amount in discount, 2 — as a percentage in discountRate. Defaults to 2
Fielditems[].discount Typenumber Requiredno DescriptionDiscount amount. Applied only when discountTypeId is 1
Fielditems[].discountRate Typenumber Requiredno DescriptionDiscount percentage. Applied only when items[].discountTypeId is 2
Fielditems[].taxRate Typenumber Requiredno DescriptionTax rate (%)
Fielditems[].taxIncluded Typeboolean Requiredno DescriptionTax included in the price

Examples

curl — personal key

Terminal
curl -X PUT "https://vibecode.bitrix24.com/v1/leads/1001333/products" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "productId": 6967, "price": 25000, "quantity": 2 },
      { "productId": 6969, "price": 5000, "quantity": 1, "discountTypeId": 1, "discount": 500 }
    ]
  }'

curl — OAuth application

Terminal
curl -X PUT "https://vibecode.bitrix24.com/v1/leads/1001333/products" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "productId": 6967, "price": 25000, "quantity": 2 },
      { "productId": 6969, "price": 5000, "quantity": 1, "discountTypeId": 1, "discount": 500 }
    ]
  }'

JavaScript — personal key

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/leads/1001333/products', {
  method: 'PUT',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { productId: 6967, price: 25000, quantity: 2 },
      { productId: 6969, price: 5000, quantity: 1, discountTypeId: 1, discount: 500 },
    ],
  }),
})

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

JavaScript — OAuth application

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/leads/1001333/products', {
  method: 'PUT',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items: [
      { productId: 6967, price: 25000, quantity: 2 },
      { productId: 6969, price: 5000, quantity: 1, discountTypeId: 1, discount: 500 },
    ],
  }),
})

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

Response fields

Field Type Description
Fieldsuccess Typeboolean DescriptionAlways true on success
Fielddata Typearray DescriptionAll rows of the lead after the replacement, each in full. Row fields — see Product fields

Response example

JSON
{
  "success": true,
  "data": [
    {
      "id": 2373,
      "ownerId": 1001333,
      "ownerType": "L",
      "productId": 6967,
      "productName": "Parent product",
      "price": 25000,
      "priceAccount": 25000,
      "priceExclusive": 25000,
      "priceNetto": 25000,
      "priceBrutto": 25000,
      "quantity": 2,
      "discountTypeId": 2,
      "discountRate": 0,
      "discount": 0,
      "taxRate": null,
      "taxIncluded": false,
      "customized": "Y",
      "measureCode": 796,
      "measureName": "pcs",
      "sort": 0,
      "xmlId": "",
      "type": 3,
      "storeId": null
    },
    {
      "id": 2375,
      "ownerId": 1001333,
      "ownerType": "L",
      "productId": 6969,
      "productName": "Service",
      "price": 5000,
      "priceAccount": 5000,
      "priceExclusive": 5000,
      "priceNetto": 5500,
      "priceBrutto": 5500,
      "quantity": 1,
      "discountTypeId": 1,
      "discountRate": 0,
      "discount": 500,
      "taxRate": null,
      "taxIncluded": false,
      "customized": "Y",
      "measureCode": 796,
      "measureName": "pcs",
      "sort": 0,
      "xmlId": "",
      "type": 7,
      "storeId": null
    }
  ]
}

Error response example

400 — invalid format:

JSON
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "items must be an array"
  }
}

Errors

HTTP Code Description
HTTP400 CodeINVALID_PARAMS DescriptionThe body contains a field name that is not among the writable fields — see Product fields
HTTP400 CodeINVALID_PARAMS Descriptionitems is not an array
HTTP400 CodeINVALID_PARAMS DescriptionAn entry in the items array carries no writable product-row field — an empty object, or read-only fields only. The error text lists the writable names
HTTP404 CodeENTITY_NOT_FOUND DescriptionLead not found. The message is Owner was not found
HTTP403 CodeSCOPE_DENIED DescriptionThe API key does not have the crm scope
HTTP403 CodeWRITE_BLOCKED_READONLY_KEY DescriptionThe key is read-only — see access rights
HTTP401 CodeTOKEN_MISSING DescriptionThe API key has no configured tokens

Full list of common API errors — Errors.

Known specifics

Full replacement: PUT replaces the entire product list. To add a row, first get the current ones (GET), add the new one to the array, and send everything back (PUT). Fetch the whole list: without limit, GET returns only the first page, so pass limit=5000 or page through with offset until meta.hasMore is false. Sending back an incomplete list erases everything it omits.

An amount discount requires discountTypeId: 1. The discount type defaults to 2, a percentage, and in that mode the discount field is not stored: the row comes back with discount: 0 and no error is raised. To set a discount as an amount, send discountTypeId: 1 together with discount. To set it as a percentage, send discountRate with type 2.

See also