Skip to content

Bulk product updates

POST /apps/catalog/products/bulk — scope write_products, Idempotency-Key required.

This route updates existing products. It does not create or delete products, and it does not touch variants, prices or stock.

{
"products": [
{ "id": "3f0c…", "name": "Wool coat", "metaTitle": "Wool coat — free shipping" },
{ "id": "9ab1…", "status": "Draft" },
{ "id": "d27e…", "slug": "" }
]
}
Field Omitted null Value
id required the product’s UUID
name unchanged unchanged new name
slug unchanged do not send new URL slug; "" derives a fresh slug from the name
metaTitle unchanged cleared new SEO title
metaDescription unchanged cleared new SEO description
categoryId unchanged cleared — the product loses its primary category primary category UUID
status unchanged do not send Active, Draft or Archived

Request-level rules — any breach rejects the whole request with 400:

  • 1 to 100 rows;
  • no product id twice in one request;
  • the caller must be an app token (a staff session is refused on this route).

Response — always 200 for a valid request

Section titled “Response — always 200 for a valid request”
{
"requested": 3,
"succeeded": 2,
"failed": 1,
"slugsChanged": 1,
"results": [
{ "id": "3f0c…", "status": "updated" },
{ "id": "9ab1…", "status": "failed", "errorCode": "not_found", "error": "" },
{ "id": "d27e…", "status": "updated", "slug": "wool-coat-2", "previousSlug": "coat" }
]
}
errorCode Meaning
not_found No product with that id in this store.
rejected The change was refused by validation; error says why.

Rows are independent: a failed row does not roll back the others.

When slugsChanged is above zero, product URLs on the storefront changed. previousSlug tells you which. If your app stores product URLs, update them from these rows.