Update a product
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Product ID
Header Parameters
Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.
- Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
- Reusing a key with a different body returns
409 Conflictwithcode: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response. - Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
- Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
- Sending this header on a
GETreturns400withcode: "IDEMPOTENCY_KEY_NOT_SUPPORTED"— GETs are already idempotent. - Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.
length <= 255Display name of the product, 1 to 500 characters.
length <= 500URL slug. Letters, numbers, and hyphens only. Changing the slug breaks existing shopper bookmarks for this product.
^[\p{L}\p{N}-]+$length <= 500Stock keeping unit, meaning your internal product code.
length <= 191Global Trade Item Number (universal barcode: EAN/UPC/ISBN). Validated against the checksum digit. Pass an empty string to clear.
Manufacturer Part Number, paired with brand when no GTIN exists. Max 70 chars.
length <= 70Sale-price effective window start (ISO 8601). Enforced at checkout: outside the window the regular price is charged. Pass null to clear.
Sale-price effective window end (ISO 8601). Pass null to clear.
Shipping weight numeric value. Pass null to clear.
0 <= valueShipping weight unit.
"kg" | "lb" | "g" | "oz"Shipping package length. Pass null to clear.
0 <= valueShipping package width. Pass null to clear.
0 <= valueShipping package height. Pass null to clear.
0 <= valueUnit shared by the dimension fields.
"cm" | "in"SEO meta description (≤160 chars). Used by Google Search snippets and social link previews. Falls back to a stripped/truncated description when omitted.
length <= 160Long-form product description. Accepts HTML, and the backend strips dangerous tags/attributes (<script>, inline event handlers, <iframe>, …) before saving. Hard cap of 65 535 characters.
length <= 65535How description is written. Omit it and the platform decides: a description that contains real HTML tags is stored as html, anything else keeps its current format (text for a new product). Send it to override that, for example text for a description that only quotes a tag. A description marked html is sanitized on write and rendered as HTML by the storefront; the SEO audit does not penalize it.
"text" | "html" | "markdown"Base list price in the store currency.
0 <= valueSale price: greater than 0 and lower than basePrice (the one sent with it, or the stored one), otherwise the request is refused with a 400 naming the field. 0 is not a sale price. When present, the storefront displays it as the active price with basePrice shown struck-through. Pass null to take the product OFF sale. Omitting the field leaves the existing sale price untouched, which is not the same thing (D76: the dashboard form sent undefined for an emptied box, so a sale could be started and never ended).
0 <= valueInternal cost (what you pay your supplier). Used for margin reporting only, and never shown to shoppers. Pass null to clear.
Publish state. active = visible to shoppers; draft = only visible in the dashboard.
"active" | "draft"Product structure. SIMPLE = single SKU; VARIABLE = parent of multiple variants.
"SIMPLE" | "VARIABLE" | "KIT"How a KIT is priced. FIXED (default) — you set basePrice and it stays put. SUM — the kit costs exactly what its components cost, recomputed on every read, so a component going on sale lowers the kit price on its own. SUM_MINUS_PERCENT — that sum less kitDiscountValue percent. Ignored on SIMPLE and VARIABLE products.
"FIXED" | "SUM" | "SUM_MINUS_PERCENT"Percent off the component sum, 0-100. Required when kitPricingMode is SUM_MINUS_PERCENT; ignored otherwise.
0 <= value <= 100If true, the product is a digital download, so checkout issues a signed URL after payment instead of triggering shipping.
When true, the product is flagged for re-sync to connected platforms on the next sync job.
Per-platform field overrides. Keyed by platform code of an installed connector app (for example TIKTOK) or by sales-channel ID (for example vc_abc123); value is a partial product object that overrides the canonical fields for that channel only.
Free-form tags for filtering and segmentation.
items <= 250Existing category IDs to assign (replaces the current set). Pass [] to remove all categories. Unknown/cross-store IDs are rejected with 400. To assign by name, creating the category if missing, use categoryNames.
items <= 100Category names to assign (case-insensitive match, auto-created if missing). Additive: names are ADDED to the product, joining the set in categories when that is also sent, otherwise to the product's current categories (they are not replaced).
items <= 100Existing brand IDs to assign (replaces the current set). Pass [] to remove all brands. Unknown/cross-store IDs are rejected with 400. To assign by name, creating the brand if missing, use brandNames.
items <= 50Brand names to assign (case-insensitive match, auto-created if missing). Additive: names are ADDED to the set in brands when that is also sent, otherwise to the product's current brands (they are not replaced).
items <= 50Product images. Each entry is an image URL string or an object { url, position, isMain }. External URLs are imported into your media library; to reuse a library asset, send its URL and key from /v1/media.
items <= 250Display order within the catalog/category listing. Lower values render first. Mirrors WooCommerce menu_order.
ID of the shipping class this product belongs to (used by shipping-rate rules). Pass null to clear.
ID of the tax class this product belongs to (drives differential tax rates at checkout). Pass null to clear (falls back to category/store-default/Standard).
Digital-download configuration. Required when isDownloadable=true. files[] are uploaded via /v1/media. downloadLimit = max downloads per order (null = unlimited). downloadExpiry = hours until the signed URL expires (null = no expiry).
Tax category. taxable is the default; exempt skips all tax calculation.
"taxable" | "exempt"Multi-language overrides. Keyed by ISO locale code (he, ar, es, …). Each value is a partial product with translated name, description, seoTitle, seoDescription and slug.
curl -X PATCH "https://api.brainerce.com/api/v1/products/string" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{}'{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Classic T-Shirt",
"slug": "classic-t-shirt",
"localeSlugs": {},
"description": "A soft cotton t-shirt.",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "24.90",
"costPrice": "12.00",
"priceMin": "24.90",
"priceMax": "34.90",
"priceVaries": true,
"displayPrice": "29.90",
"displaySalePrice": "24.90",
"displayPriceMin": "24.90",
"displayPriceMax": "34.90",
"displayCurrency": "USD",
"gtin": "0123456789012",
"mpn": "TS-BLK-M",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0.3,
"shippingWeightUnit": "kg",
"shippingLengthValue": 30,
"shippingWidthValue": 20,
"shippingHeightValue": 5,
"shippingDimensionUnit": "cm",
"metaDescription": "Soft cotton t-shirt in five colors",
"status": "active",
"sku": "TSHIRT-BLK-M",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 10,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "photos.zip",
"url": "https://cdn.example.com/downloads/photos.zip",
"storageKey": "downloads/photos.zip",
"size": 5242880,
"mimeType": "application/zip"
}
],
"downloadLimit": 3,
"downloadExpiry": 30
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "taxable",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"variants": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"productId": "clx1a2b3c4d5e6f7g8h9",
"sku": "TSHIRT-BLK-M",
"name": "Black / M",
"attributes": {
"size": "S",
"color": "Red"
},
"price": "19.90",
"salePrice": "24.90",
"costPrice": "12.00",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"displayPrice": "29.90",
"displaySalePrice": "24.90",
"displayCurrency": "USD",
"image": "string",
"position": 0,
"status": "active",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "photos.zip",
"url": "https://cdn.example.com/downloads/photos.zip",
"storageKey": "downloads/photos.zip",
"size": 5242880,
"mimeType": "application/zip"
}
],
"downloadLimit": 3,
"downloadExpiry": 30
},
"shippingClassId": "clx1a2b3c4d5e6f7g8h9",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "https://cdn.example.com/products/tshirt.jpg",
"position": 0,
"isMain": true,
"alt": "Black t-shirt on a hanger",
"id": "clx1a2b3c4d5e6f7g8h9",
"key": "products/tshirt.jpg",
"thumbnailUrl": "https://cdn.example.com/products/tshirt-thumb.jpg",
"width": 1200,
"height": 800,
"size": 245123,
"mimeType": "image/jpeg",
"createdAt": "2019-08-24T14:15:22Z",
"source": "local",
"importStatus": "completed",
"importError": "HTTP 404 from the source server",
"src": "https://cdn.example.com/products/tshirt.jpg",
"altSource": "asset",
"altTranslations": {},
"originalFilename": "tshirt.jpg",
"sourceUrl": "https://source.example.com/img/tshirt.jpg",
"sourcePlatform": "SHOPIFY",
"importedAt": "2026-01-15T10:30:00.000Z",
"platformImageIds": {}
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Shoes",
"taxBehavior": "taxable"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"attributeId": "clx1a2b3c4d5e6f7g8h9",
"attributeOptionId": "clx1a2b3c4d5e6f7g8h9",
"platform": "GLOBAL",
"attribute": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Color",
"displayType": "COLOR_SWATCH"
},
"attributeOption": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Black",
"value": "black",
"swatchColor": "#000000",
"swatchColor2": "#FFFFFF",
"swatchImageUrl": "https://cdn.example.com/swatches/black.png"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"definitionId": "clx1a2b3c4d5e6f7g8h9",
"definitionKey": "material",
"definitionName": "Material",
"type": "TEXT",
"value": "Cotton",
"variantId": "clx1a2b3c4d5e6f7g8h9"
}
],
"customizationFields": [
{
"definitionId": "clx1a2b3c4d5e6f7g8h9",
"name": "Engraving text",
"key": "engraving",
"description": "Up to 20 characters",
"type": "TEXT",
"required": true,
"minLength": 1,
"maxLength": 20,
"minValue": 0,
"maxValue": 100,
"enumValues": [
"string"
],
"defaultValue": "Happy birthday",
"position": 0
}
],
"channelPublishes": [
{
"salesChannel": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
},
"connection": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
}
}
],
"vibeCodedPublishes": [
{
"salesChannel": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
},
"connection": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
}
}
],
"channelSync": {},
"shippingClassId": "clx1a2b3c4d5e6f7g8h9",
"shippingClass": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Heavy items"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 1,
"orderBumpCount": 1,
"avgRating": 4.6,
"reviewCount": 18,
"seoScore": {
"score": 82,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "clx1a2b3c4d5e6f7g8h9",
"groupId": "clx1a2b3c4d5e6f7g8h9",
"variantId": "clx1a2b3c4d5e6f7g8h9",
"position": 0,
"minOverride": 0,
"maxOverride": 3,
"freeQuantityOverride": 1,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "Toppings",
"internalName": "toppings-pizza",
"description": "Choose up to 3",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 3,
"freeQuantity": 1,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "active",
"modifiers": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Extra cheese",
"description": "Double portion",
"translations": {},
"priceDelta": "2.50",
"sku": "TSHIRT-BLK-M",
"image": "http://example.com",
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "clx1a2b3c4d5e6f7g8h9",
"status": "active"
}
]
}
]
}{
"statusCode": 400,
"code": "VALIDATION_FAILED",
"message": "name should not be empty, price must be a positive number",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}"
}{
"statusCode": 403,
"code": "INSUFFICIENT_SCOPE",
"message": "API key does not have the required scope: products:write",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}"
}{
"statusCode": 409,
"code": "CONFLICT",
"message": "The request conflicts with the current state of the resource",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/store"
}