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.
Request
Section titled “Request”{ "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
idtwice 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.
Slugs change URLs
Section titled “Slugs change URLs”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.