> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.brevo.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.brevo.com/_mcp/server.

# Create products in batch

POST https://api.brevo.com/v3/products/batch
Content-Type: application/json

Create or update multiple ecommerce products in a single request. The `products` array accepts up to 100 product objects for creation (or up to 1000 when `updateEnabled` is `true` and the account has an increased limit). Each product requires a unique `id` and `name` (name is mandatory for creation only). When `updateEnabled` is `false`, all products are inserted as new; if any ID already exists, a `400` error is returned. When `updateEnabled` is `true`, existing products are updated and new ones are created via upsert. Duplicate IDs within the same request payload are rejected. The response returns the count of created and updated products.

Reference: https://developers.brevo.com/reference/create-update-batch-products

## Authentication

- `api-key` header (required) — The API key should be passed in the request headers as `api-key` for authentication.

## Request

### Body (application/json)

This endpoint expects an object.

- `products` (list of object, required) — array of products objects
  - `id` (string, required) — Product ID for which you requested the details
  - `name` (string, required) — **Mandatory in case of creation**. Name of the product, as displayed in the shop
  - `brand` (string, optional) — Brand of the product
  - `categories` (list of string, optional) — Category ID-s of the product
  - `deletedAt` (string, optional) — UTC date-time (YYYY-MM-DDTHH:mm:ss.SSSZ) of the product deleted from the shop's database
  - `description` (string, optional) — Description of the product
  - `imageUrl` (string, optional) — Absolute URL to the cover image of the product
  - `isDeleted` (boolean, optional) — product deleted from the shop's database
  - `metaInfo` (map from string to string or integer, optional) — Meta data of product such as description, vendor, producer, stock level. The total characters of cumulative metaInfo shall not exceed **20000 characters**.
  - `parentId` (string, optional) — Parent product id of the product
  - `price` (float, optional) — Price of the product
  - `alternativePrice` (float, optional) — Alternative price of the product
  - `sku` (string, optional) — Product identifier from the shop
  - `stock` (double, optional) — Current stock value of the product from the shop's database
  - `url` (string, optional) — URL to the product
- `updateEnabled` (boolean, optional) — Facilitate to update the existing products in the same request (updateEnabled = true)

## Response

### 201

Products created and updated

- `createdCount` (long, optional) — Number of the new created products
- `updatedCount` (long, optional) — Number of the existing products updated

## Errors

### 400 Bad Request Error

bad request

- `code` (enum, required) — Error code displayed in case of a failure
  - Allowed values: `invalid_parameter`, `missing_parameter`, `out_of_range`, `campaign_processing`, `campaign_sent`, `document_not_found`, `not_enough_credits`, `permission_denied`, `duplicate_parameter`, `duplicate_request`, `method_not_allowed`, `unauthorized`, `account_under_validation`, `not_acceptable`, `bad_request`, `unprocessable_entity`, `Domain does not exist`, `Contact email not found`, `Attribute not found`, `Category id not found`, `Invalid parameters passed`, `Record(s) for identifier not found`, `Returned when query params are invalid`, `Returned when invalid data posted`, `Feed not found`, `Campaign ID not found`, `api-key not found`, `DMARC policy requires domain authentication`, `DNS records not properly configured`, `Invalid OTP code provided`, `OTP code has expired`, `Domain already exists in your account`, `The sum of all IP weights must equal 100`, `Authentication failed`, `Insufficient credits`, `Request already processed`
- `message` (string, required) — Readable message associated to the failure

## Examples

**Request**

```json
{
  "products": [
    {
      "id": "P11",
      "name": "Iphone 11"
    }
  ]
}
```

**Response**

```json
{
  "createdCount": 2,
  "updatedCount": 7
}
```

**SDK Code**

```typescript response
import { BrevoClient } from "@getbrevo/brevo";

async function main() {
    const client = new BrevoClient({
        apiKey: "YOUR_API_KEY_HERE",
    });
    await client.ecommerce.createUpdateBatchProducts({
        products: [
            {
                id: "P11",
                name: "Iphone 11",
            },
        ],
    });
}
main();

```

```python response
from brevo import Brevo
from brevo.ecommerce import CreateUpdateBatchProductsRequestProductsItem

client = Brevo(
    api_key="YOUR_API_KEY_HERE",
)

client.ecommerce.create_update_batch_products(
    products=[
        CreateUpdateBatchProductsRequestProductsItem(
            id="P11",
            name="Iphone 11",
        )
    ],
)

```

```php response
<?php

namespace Example;

use Brevo\Brevo;
use Brevo\Ecommerce\Requests\CreateUpdateBatchProductsRequest;
use Brevo\Ecommerce\Types\CreateUpdateBatchProductsRequestProductsItem;

$client = new Brevo(
    apiKey: 'YOUR_API_KEY_HERE',
);
$client->ecommerce->createUpdateBatchProducts(
    new CreateUpdateBatchProductsRequest([
        'products' => [
            new CreateUpdateBatchProductsRequestProductsItem([
                'id' => 'P11',
                'name' => 'Iphone 11',
            ]),
        ],
    ]),
);

```

```go response
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.brevo.com/v3/products/batch"

	payload := strings.NewReader("{\n  \"products\": [\n    {\n      \"id\": \"P11\",\n      \"name\": \"Iphone 11\"\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("api-key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby response
require 'uri'
require 'net/http'

url = URI("https://api.brevo.com/v3/products/batch")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"products\": [\n    {\n      \"id\": \"P11\",\n      \"name\": \"Iphone 11\"\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java response
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.brevo.com/v3/products/batch")
  .header("api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"products\": [\n    {\n      \"id\": \"P11\",\n      \"name\": \"Iphone 11\"\n    }\n  ]\n}")
  .asString();
```

```csharp response
using RestSharp;

var client = new RestClient("https://api.brevo.com/v3/products/batch");
var request = new RestRequest(Method.POST);
request.AddHeader("api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"products\": [\n    {\n      \"id\": \"P11\",\n      \"name\": \"Iphone 11\"\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift response
import Foundation

let headers = [
  "api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = ["products": [
    [
      "id": "P11",
      "name": "Iphone 11"
    ]
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.brevo.com/v3/products/batch")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```