Products
Products are the items sold in your store. Each product can be a simple SKU or a parent of multiple variants (size, color, …) — variants carry their own pricing and inventory. Products support modifiers (configurable options like engraving), tags, brands, categories, and SEO metadata. Use these endpoints to manage your catalogue and publish to sales channels.
Products are served on the public API surface only — every operation below is under /v1 and takes Authorization: Bearer brainerce_*. There is no separate dashboard path for products.
Product metafield values live on Metafields, and product reviews on Product reviews.
Upload a review photo
For /api/vc/{connectionId}/* routes the connectionId (vc_*) is in the URL path — not a header. Live-mode requests must also send a matching Origin header.
In: header
Path Parameters
Header Parameters
curl -X POST "https://api.brainerce.com/api/vc/string/products/string/review-photo" \ -H "origin: string" \ -H "authorization: string"{
"url": "https://cdn.brainerce.com/customizations/abc123.png",
"key": "customizations/abc123.png",
"width": 1200,
"height": 800
}List products with pagination
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Query Parameters
curl -X GET "https://api.brainerce.com/api/v1/products?page=0&limit=0&search=string&status=string&type=string&sortBy=string&sortOrder=string®ionId=string"{
"data": [
{
"id": "string",
"name": "string",
"slug": "string",
"localeSlugs": {
"property1": "string",
"property2": "string"
},
"description": "string",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "string",
"costPrice": "string",
"priceMin": "string",
"priceMax": "string",
"priceVaries": true,
"displayPrice": "string",
"displaySalePrice": "string",
"displayPriceMin": "string",
"displayPriceMax": "string",
"displayCurrency": "string",
"gtin": "string",
"mpn": "string",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0,
"shippingWeightUnit": "string",
"shippingLengthValue": 0,
"shippingWidthValue": 0,
"shippingHeightValue": 0,
"shippingDimensionUnit": "string",
"metaDescription": "string",
"status": "string",
"sku": "string",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 0,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "string",
"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": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "string",
"position": 0,
"isMain": true,
"alt": "string",
"id": "string",
"key": "string",
"thumbnailUrl": "string",
"width": 0,
"height": 0,
"size": 0,
"mimeType": "string",
"createdAt": "2019-08-24T14:15:22Z",
"source": "string",
"importStatus": "string",
"importError": "string"
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "string",
"name": "string",
"taxBehavior": "string"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "string",
"attributeId": "string",
"attributeOptionId": "string",
"platform": "string",
"attribute": {
"id": "string",
"name": "string",
"displayType": "string"
},
"attributeOption": {
"id": "string",
"name": "string",
"value": "string",
"swatchColor": "string",
"swatchColor2": "string",
"swatchImageUrl": "string"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "string",
"definitionId": "string",
"definitionKey": "string",
"definitionName": "string",
"type": "string",
"value": "string",
"variantId": "string"
}
],
"customizationFields": [
{
"definitionId": "string",
"name": "string",
"key": "string",
"description": "string",
"type": "string",
"required": true,
"minLength": 0,
"maxLength": 0,
"minValue": 0,
"maxValue": 0,
"enumValues": [
"string"
],
"defaultValue": "string",
"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": "string",
"shippingClass": {
"id": "string",
"name": "string"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 0,
"orderBumpCount": 0,
"avgRating": 0,
"reviewCount": 0,
"seoScore": {
"score": 0,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "string",
"groupId": "string",
"variantId": "string",
"position": 0,
"minOverride": 0,
"maxOverride": 0,
"freeQuantityOverride": 0,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "string",
"internalName": "string",
"description": "string",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 0,
"freeQuantity": 0,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "string",
"modifiers": [
{
"id": "string",
"name": "string",
"description": "string",
"translations": {},
"priceDelta": "2.50",
"sku": "string",
"image": {},
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "string",
"status": "string"
}
]
}
]
}
],
"meta": {
"page": 1,
"limit": 20,
"total": 137,
"totalPages": 7
}
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products"
}{
"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"
}{
"statusCode": 429,
"code": "RATE_LIMITED",
"message": "Too many requests",
"details": {
"retryAfterSeconds": 12
},
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products"
}Create a new product
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
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. Required.
URL slug. Letters, numbers, and hyphens only. Auto-generated from name when omitted.
Stock keeping unit, meaning your internal product code.
Global Trade Item Number (universal barcode: EAN/UPC/ISBN). Required by Google Shopping, Facebook Catalog, and TikTok Shop on most categories. Validated against the checksum digit.
Manufacturer Part Number, paired with brand when no GTIN exists. Max 70 chars.
Your own stable identifier for this product in the SOURCE system (supplier feed, legacy store, ERP). Unique per store. Its purpose is safe retries on bulk imports: re-sending a batch after a timeout matches on this value and skips the product instead of creating a duplicate. Not a marketplace id: those are managed per-connection and are not settable here.
length <= 191Sale-price effective window start (ISO 8601). Google Merchant Center sale_price_effective_date. Both this and salePriceEndsAt must be set together, or neither applies.
Sale-price effective window end (ISO 8601). See salePriceStartsAt.
Shipping weight numeric value. Paired with shippingWeightUnit.
Shipping weight unit.
"kg" | "lb" | "g" | "oz"Shipping package length. Paired with shippingDimensionUnit.
Shipping package width. Paired with shippingDimensionUnit.
Shipping package height. Paired with shippingDimensionUnit.
Unit shared by shippingLengthValue/shippingWidthValue/shippingHeightValue.
"cm" | "in"SEO meta description (≤160 chars). Used by Google Search snippets, social link previews (WhatsApp/Telegram/Twitter), email previews, and schema.org JSON-LD. 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 <= 65535Base list price in the store currency. Must be ≥ 0.
0 <= valueSale price (must be < basePrice). When present, the storefront displays it as the active price with basePrice shown struck-through.
0 <= valueInternal cost (what you pay your supplier). Used for margin reporting only, and never shown to shoppers.
0 <= valuePublish state. active = visible to shoppers; draft = only visible in the dashboard.
"active" | "draft"Product structure. SIMPLE = single SKU; VARIABLE = parent of multiple variants (size, color, …); KIT = a bundle priced from component products. A KIT may be created with no components yet — it is simply not purchasable until it has at least one.
"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.
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).
Empty Object
Inventory configuration for SIMPLE products. total = current stock count; trackingMode: TRACKED (default) decrements on each sale and blocks oversells; UNLIMITED accepts any order; DISABLED hides stock UI. Ignored for VARIABLE products, whose variants carry their own inventory.
Empty Object
Array of variants. Required (and non-empty) for VARIABLE products, at most 500. Each variant carries its own price, SKU, and inventory.
Array of existing category IDs (from GET /categories / list_categories). Unknown/cross-store IDs are rejected with 400. To assign by name, creating the category if it does not exist, use categoryNames instead.
Category names to assign. Each name is matched case-insensitively to an existing category and auto-created if none exists, so there is no need to look up IDs first. Merged with any IDs passed in categories.
Array of existing brand IDs. Unknown/cross-store IDs are rejected with 400. To assign by name, creating the brand if it does not exist, use brandNames instead.
Brand names to assign. Each name is matched case-insensitively to an existing brand and auto-created if none exists. Merged with any IDs passed in brands.
Free-form tags for filtering and segmentation.
Product images. Each entry: { url, position, isMain }. Upload via /v1/media first to get a signed URL.
Sales channels this product should publish to. Each platform syncs the product separately and can have per-channel overrides (see channels).
Per-platform field overrides. Keyed by platform code, value is a partial product object that overrides the canonical fields for that channel only.
Empty Object
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 / metaDescription. Falls back to the canonical fields for missing locales.
Empty Object
curl -X POST "https://api.brainerce.com/api/v1/products" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "name": "Premium Wireless Headphones", "basePrice": 299.99, "type": "SIMPLE" }'{
"id": "string",
"name": "string",
"slug": "string",
"localeSlugs": {
"property1": "string",
"property2": "string"
},
"description": "string",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "string",
"costPrice": "string",
"priceMin": "string",
"priceMax": "string",
"priceVaries": true,
"displayPrice": "string",
"displaySalePrice": "string",
"displayPriceMin": "string",
"displayPriceMax": "string",
"displayCurrency": "string",
"gtin": "string",
"mpn": "string",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0,
"shippingWeightUnit": "string",
"shippingLengthValue": 0,
"shippingWidthValue": 0,
"shippingHeightValue": 0,
"shippingDimensionUnit": "string",
"metaDescription": "string",
"status": "string",
"sku": "string",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 0,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "string",
"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": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "string",
"position": 0,
"isMain": true,
"alt": "string",
"id": "string",
"key": "string",
"thumbnailUrl": "string",
"width": 0,
"height": 0,
"size": 0,
"mimeType": "string",
"createdAt": "2019-08-24T14:15:22Z",
"source": "string",
"importStatus": "string",
"importError": "string"
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "string",
"name": "string",
"taxBehavior": "string"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "string",
"attributeId": "string",
"attributeOptionId": "string",
"platform": "string",
"attribute": {
"id": "string",
"name": "string",
"displayType": "string"
},
"attributeOption": {
"id": "string",
"name": "string",
"value": "string",
"swatchColor": "string",
"swatchColor2": "string",
"swatchImageUrl": "string"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "string",
"definitionId": "string",
"definitionKey": "string",
"definitionName": "string",
"type": "string",
"value": "string",
"variantId": "string"
}
],
"customizationFields": [
{
"definitionId": "string",
"name": "string",
"key": "string",
"description": "string",
"type": "string",
"required": true,
"minLength": 0,
"maxLength": 0,
"minValue": 0,
"maxValue": 0,
"enumValues": [
"string"
],
"defaultValue": "string",
"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": "string",
"shippingClass": {
"id": "string",
"name": "string"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 0,
"orderBumpCount": 0,
"avgRating": 0,
"reviewCount": 0,
"seoScore": {
"score": 0,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "string",
"groupId": "string",
"variantId": "string",
"position": 0,
"minOverride": 0,
"maxOverride": 0,
"freeQuantityOverride": 0,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "string",
"internalName": "string",
"description": "string",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 0,
"freeQuantity": 0,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "string",
"modifiers": [
{
"id": "string",
"name": "string",
"description": "string",
"translations": {},
"priceDelta": "2.50",
"sku": "string",
"image": {},
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "string",
"status": "string"
}
]
}
]
}{
"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"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products"
}{
"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"
}Get a single product by ID
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Product ID
Query Parameters
curl -X GET "https://api.brainerce.com/api/v1/products/string?regionId=string"{
"id": "string",
"name": "string",
"slug": "string",
"localeSlugs": {
"property1": "string",
"property2": "string"
},
"description": "string",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "string",
"costPrice": "string",
"priceMin": "string",
"priceMax": "string",
"priceVaries": true,
"displayPrice": "string",
"displaySalePrice": "string",
"displayPriceMin": "string",
"displayPriceMax": "string",
"displayCurrency": "string",
"gtin": "string",
"mpn": "string",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0,
"shippingWeightUnit": "string",
"shippingLengthValue": 0,
"shippingWidthValue": 0,
"shippingHeightValue": 0,
"shippingDimensionUnit": "string",
"metaDescription": "string",
"status": "string",
"sku": "string",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 0,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "string",
"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": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "string",
"position": 0,
"isMain": true,
"alt": "string",
"id": "string",
"key": "string",
"thumbnailUrl": "string",
"width": 0,
"height": 0,
"size": 0,
"mimeType": "string",
"createdAt": "2019-08-24T14:15:22Z",
"source": "string",
"importStatus": "string",
"importError": "string"
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "string",
"name": "string",
"taxBehavior": "string"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "string",
"attributeId": "string",
"attributeOptionId": "string",
"platform": "string",
"attribute": {
"id": "string",
"name": "string",
"displayType": "string"
},
"attributeOption": {
"id": "string",
"name": "string",
"value": "string",
"swatchColor": "string",
"swatchColor2": "string",
"swatchImageUrl": "string"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "string",
"definitionId": "string",
"definitionKey": "string",
"definitionName": "string",
"type": "string",
"value": "string",
"variantId": "string"
}
],
"customizationFields": [
{
"definitionId": "string",
"name": "string",
"key": "string",
"description": "string",
"type": "string",
"required": true,
"minLength": 0,
"maxLength": 0,
"minValue": 0,
"maxValue": 0,
"enumValues": [
"string"
],
"defaultValue": "string",
"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": "string",
"shippingClass": {
"id": "string",
"name": "string"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 0,
"orderBumpCount": 0,
"avgRating": 0,
"reviewCount": 0,
"seoScore": {
"score": 0,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "string",
"groupId": "string",
"variantId": "string",
"position": 0,
"minOverride": 0,
"maxOverride": 0,
"freeQuantityOverride": 0,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "string",
"internalName": "string",
"description": "string",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 0,
"freeQuantity": 0,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "string",
"modifiers": [
{
"id": "string",
"name": "string",
"description": "string",
"translations": {},
"priceDelta": "2.50",
"sku": "string",
"image": {},
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "string",
"status": "string"
}
]
}
]
}{
"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}"
}Update a product
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → 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.
URL slug. Letters, numbers, and hyphens only. Changing the slug breaks existing shopper bookmarks for this product.
Stock keeping unit, meaning your internal product code.
Global 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). Pass null to clear.
Sale-price effective window end (ISO 8601). Pass null to clear.
Shipping weight numeric value. Pass null to clear.
Shipping weight unit.
"kg" | "lb" | "g" | "oz"Shipping package length. Pass null to clear.
Shipping package width. Pass null to clear.
Shipping package height. Pass null to clear.
Unit 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 <= 65535Base list price in the store currency.
0 <= valueSale price (must be < basePrice). 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 (SHOPIFY, TIKTOK, …); value is a partial product object that overrides the canonical fields for that channel only.
Empty Object
Free-form tags for filtering and segmentation.
Existing 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.
Category 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).
Existing 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.
Brand 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).
Product images. Each entry: { url, position, isMain }. Upload via /v1/media first to get a signed URL.
Display 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).
Empty Object
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 / metaDescription.
Empty Object
curl -X PATCH "https://api.brainerce.com/api/v1/products/string" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{}'{
"id": "string",
"name": "string",
"slug": "string",
"localeSlugs": {
"property1": "string",
"property2": "string"
},
"description": "string",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "string",
"costPrice": "string",
"priceMin": "string",
"priceMax": "string",
"priceVaries": true,
"displayPrice": "string",
"displaySalePrice": "string",
"displayPriceMin": "string",
"displayPriceMax": "string",
"displayCurrency": "string",
"gtin": "string",
"mpn": "string",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0,
"shippingWeightUnit": "string",
"shippingLengthValue": 0,
"shippingWidthValue": 0,
"shippingHeightValue": 0,
"shippingDimensionUnit": "string",
"metaDescription": "string",
"status": "string",
"sku": "string",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 0,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "string",
"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": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "string",
"position": 0,
"isMain": true,
"alt": "string",
"id": "string",
"key": "string",
"thumbnailUrl": "string",
"width": 0,
"height": 0,
"size": 0,
"mimeType": "string",
"createdAt": "2019-08-24T14:15:22Z",
"source": "string",
"importStatus": "string",
"importError": "string"
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "string",
"name": "string",
"taxBehavior": "string"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "string",
"attributeId": "string",
"attributeOptionId": "string",
"platform": "string",
"attribute": {
"id": "string",
"name": "string",
"displayType": "string"
},
"attributeOption": {
"id": "string",
"name": "string",
"value": "string",
"swatchColor": "string",
"swatchColor2": "string",
"swatchImageUrl": "string"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "string",
"definitionId": "string",
"definitionKey": "string",
"definitionName": "string",
"type": "string",
"value": "string",
"variantId": "string"
}
],
"customizationFields": [
{
"definitionId": "string",
"name": "string",
"key": "string",
"description": "string",
"type": "string",
"required": true,
"minLength": 0,
"maxLength": 0,
"minValue": 0,
"maxValue": 0,
"enumValues": [
"string"
],
"defaultValue": "string",
"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": "string",
"shippingClass": {
"id": "string",
"name": "string"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 0,
"orderBumpCount": 0,
"avgRating": 0,
"reviewCount": 0,
"seoScore": {
"score": 0,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "string",
"groupId": "string",
"variantId": "string",
"position": 0,
"minOverride": 0,
"maxOverride": 0,
"freeQuantityOverride": 0,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "string",
"internalName": "string",
"description": "string",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 0,
"freeQuantity": 0,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "string",
"modifiers": [
{
"id": "string",
"name": "string",
"description": "string",
"translations": {},
"priceDelta": "2.50",
"sku": "string",
"image": {},
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "string",
"status": "string"
}
]
}
]
}{
"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}"
}Delete a product
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Product ID
Query Parameters
Platforms to delete from (comma-separated)
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 <= 255curl -X DELETE "https://api.brainerce.com/api/v1/products/string?platforms=string" \ -H "Idempotency-Key: string"{
"success": true,
"jobIds": [
"string"
],
"warnings": [
"string"
]
}{
"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}"
}Create many products in one call (queued)
Queues a batch of products for creation and returns a jobId immediately. It does NOT wait for the import to finish. Every field accepted by POST /v1/products is accepted per row, including variants, categories, brands, tags, images, translations and tax behavior. Max 1000 products per request (500 recommended). For a 3,000-50,000 product catalog, send several requests of that size carrying the same importId, then poll GET /v1/products/bulk/import/{importId} for the whole import. Rows whose sku or externalId already exist are skipped by default (see conflictStrategy), so re-sending a batch after a timeout does not duplicate the catalog. The Idempotency-Key header is honored as well and returns the original jobId on replay.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
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 <= 255Products to create. Each entry accepts every field POST /v1/products accepts. Max 1000 per request (500 recommended), so chunk larger catalogs and pass the same importId on every chunk. Rows are validated individually: an invalid row is reported as a row failure and does NOT reject the batch.
Groups several chunked requests into one logical import so status can be read for the whole catalog rather than per chunk. Generate one value client-side and send it on every chunk. Any string you choose; it is not validated against anything.
length <= 191How to handle a row whose sku or externalId already exists in the store. skip (default) counts it under skipped and moves on, which is the safe choice for retries. error records it as a failure instead, which is what you want when a duplicate means the source file is wrong.
"skip" | "error"Channel sync behavior. coalesced (default) suppresses the per-product connector push and files ONE sync per affected sales channel when the import finishes. That is necessary because connectors are rate-limited per catalog (Meta allows 100 batch requests/hour). none writes to Brainerce only and leaves channels to the next reconciliation.
"coalesced" | "none"Durable dedup key for this batch. Re-sending the same key returns the ORIGINAL jobId instead of importing again. On HTTP the Idempotency-Key header does the same thing and is preferred; this field exists because it is stored in the database rather than in Redis with a 24h TTL, and because the MCP tool has no HTTP headers to carry one.
length <= 191curl -X POST "https://api.brainerce.com/api/v1/products/bulk" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "products": [ { "name": "Premium Wireless Headphones", "basePrice": 299.99, "type": "SIMPLE" } ] }'{
"jobId": "string",
"importId": "string",
"status": "QUEUED",
"total": 0,
"replayed": true
}{
"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/bulk"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/bulk"
}{
"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/bulk"
}{
"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/bulk"
}Get bulk product creation status
Counters for one queued batch: total, processed, succeeded, failed, skipped, pending. skipped counts rows whose sku/externalId already existed. They are NOT failures and NOT creations. Status is QUEUED, RUNNING, COMPLETED, COMPLETED_WITH_ERRORS, FAILED or CANCELLED; COMPLETED_WITH_ERRORS means every row was attempted and some failed, which is not something to retry wholesale. read the per-row failures from the errors endpoint instead.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Job ID returned by POST /v1/products/bulk
curl -X GET "https://api.brainerce.com/api/v1/products/bulk/string"{
"jobId": "string",
"importId": "string",
"status": "string",
"total": 0,
"processed": 0,
"succeeded": 0,
"failed": 0,
"skipped": 0,
"pending": 0,
"conflictStrategy": "string",
"syncMode": "string",
"errorMessage": "string",
"startedAt": "2019-08-24T14:15:22Z",
"finishedAt": "2019-08-24T14:15:22Z",
"createdAt": "2019-08-24T14:15:22Z",
"jobCount": 0
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/bulk/{jobId}"
}Get aggregate status for a chunked import
Rolls every batch sharing this importId into one set of counters, so a 50,000-product catalog sent as 100 requests is polled once rather than 100 times. The aggregate status is the least-complete state across the chunks: any chunk still QUEUED or RUNNING keeps the whole import unfinished. finishedAt stays null until every chunk has finished.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
The importId sent on every chunk
curl -X GET "https://api.brainerce.com/api/v1/products/bulk/import/string"{
"jobId": "string",
"importId": "string",
"status": "string",
"total": 0,
"processed": 0,
"succeeded": 0,
"failed": 0,
"skipped": 0,
"pending": 0,
"conflictStrategy": "string",
"syncMode": "string",
"errorMessage": "string",
"startedAt": "2019-08-24T14:15:22Z",
"finishedAt": "2019-08-24T14:15:22Z",
"createdAt": "2019-08-24T14:15:22Z",
"jobCount": 0
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/bulk/import/{importId}"
}List per-product failures for a bulk create job
One entry per failed product, carrying the 1-indexed row from your submitted array plus sku, externalId, productName, a code (VALIDATION / DUPLICATE / PLAN_LIMIT / INTERNAL) and the message. Every failure is recorded and nothing is truncated, so this is paginated.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Job ID returned by POST /v1/products/bulk
Query Parameters
Page number (default 1)
Page size (default 50, max 100)
curl -X GET "https://api.brainerce.com/api/v1/products/bulk/string/errors?page=string&limit=string"{
"data": [
{
"row": 0,
"sku": "string",
"externalId": "string",
"productName": "string",
"code": "string",
"message": "string"
}
],
"meta": {
"page": 1,
"limit": 20,
"total": 137,
"totalPages": 7
}
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/bulk/{jobId}/errors"
}Convert a SIMPLE product to VARIABLE
Converts the product type and migrates existing price/inventory into a first variant. Idempotent, and returns 400 if already VARIABLE.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Product ID
curl -X PATCH "https://api.brainerce.com/api/v1/products/string/convert-to-variable"{
"id": "string",
"name": "string",
"slug": "string",
"localeSlugs": {
"property1": "string",
"property2": "string"
},
"description": "string",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "string",
"costPrice": "string",
"priceMin": "string",
"priceMax": "string",
"priceVaries": true,
"displayPrice": "string",
"displaySalePrice": "string",
"displayPriceMin": "string",
"displayPriceMax": "string",
"displayCurrency": "string",
"gtin": "string",
"mpn": "string",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0,
"shippingWeightUnit": "string",
"shippingLengthValue": 0,
"shippingWidthValue": 0,
"shippingHeightValue": 0,
"shippingDimensionUnit": "string",
"metaDescription": "string",
"status": "string",
"sku": "string",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 0,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "string",
"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": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "string",
"position": 0,
"isMain": true,
"alt": "string",
"id": "string",
"key": "string",
"thumbnailUrl": "string",
"width": 0,
"height": 0,
"size": 0,
"mimeType": "string",
"createdAt": "2019-08-24T14:15:22Z",
"source": "string",
"importStatus": "string",
"importError": "string"
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "string",
"name": "string",
"taxBehavior": "string"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "string",
"attributeId": "string",
"attributeOptionId": "string",
"platform": "string",
"attribute": {
"id": "string",
"name": "string",
"displayType": "string"
},
"attributeOption": {
"id": "string",
"name": "string",
"value": "string",
"swatchColor": "string",
"swatchColor2": "string",
"swatchImageUrl": "string"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "string",
"definitionId": "string",
"definitionKey": "string",
"definitionName": "string",
"type": "string",
"value": "string",
"variantId": "string"
}
],
"customizationFields": [
{
"definitionId": "string",
"name": "string",
"key": "string",
"description": "string",
"type": "string",
"required": true,
"minLength": 0,
"maxLength": 0,
"minValue": 0,
"maxValue": 0,
"enumValues": [
"string"
],
"defaultValue": "string",
"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": "string",
"shippingClass": {
"id": "string",
"name": "string"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 0,
"orderBumpCount": 0,
"avgRating": 0,
"reviewCount": 0,
"seoScore": {
"score": 0,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "string",
"groupId": "string",
"variantId": "string",
"position": 0,
"minOverride": 0,
"maxOverride": 0,
"freeQuantityOverride": 0,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "string",
"internalName": "string",
"description": "string",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 0,
"freeQuantity": 0,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "string",
"modifiers": [
{
"id": "string",
"name": "string",
"description": "string",
"translations": {},
"priceDelta": "2.50",
"sku": "string",
"image": {},
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "string",
"status": "string"
}
]
}
]
}{
"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}/convert-to-variable"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/convert-to-variable"
}{
"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}/convert-to-variable"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/convert-to-variable"
}Convert a VARIABLE product back to SIMPLE
Merges the last remaining variant back into the parent product. Returns 400 if the product still has multiple variants, so delete the extras first.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Product ID
curl -X PATCH "https://api.brainerce.com/api/v1/products/string/convert-to-simple"{
"id": "string",
"name": "string",
"slug": "string",
"localeSlugs": {
"property1": "string",
"property2": "string"
},
"description": "string",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "string",
"costPrice": "string",
"priceMin": "string",
"priceMax": "string",
"priceVaries": true,
"displayPrice": "string",
"displaySalePrice": "string",
"displayPriceMin": "string",
"displayPriceMax": "string",
"displayCurrency": "string",
"gtin": "string",
"mpn": "string",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0,
"shippingWeightUnit": "string",
"shippingLengthValue": 0,
"shippingWidthValue": 0,
"shippingHeightValue": 0,
"shippingDimensionUnit": "string",
"metaDescription": "string",
"status": "string",
"sku": "string",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 0,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "string",
"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": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "string",
"position": 0,
"isMain": true,
"alt": "string",
"id": "string",
"key": "string",
"thumbnailUrl": "string",
"width": 0,
"height": 0,
"size": 0,
"mimeType": "string",
"createdAt": "2019-08-24T14:15:22Z",
"source": "string",
"importStatus": "string",
"importError": "string"
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "string",
"name": "string",
"taxBehavior": "string"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "string",
"attributeId": "string",
"attributeOptionId": "string",
"platform": "string",
"attribute": {
"id": "string",
"name": "string",
"displayType": "string"
},
"attributeOption": {
"id": "string",
"name": "string",
"value": "string",
"swatchColor": "string",
"swatchColor2": "string",
"swatchImageUrl": "string"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "string",
"definitionId": "string",
"definitionKey": "string",
"definitionName": "string",
"type": "string",
"value": "string",
"variantId": "string"
}
],
"customizationFields": [
{
"definitionId": "string",
"name": "string",
"key": "string",
"description": "string",
"type": "string",
"required": true,
"minLength": 0,
"maxLength": 0,
"minValue": 0,
"maxValue": 0,
"enumValues": [
"string"
],
"defaultValue": "string",
"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": "string",
"shippingClass": {
"id": "string",
"name": "string"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 0,
"orderBumpCount": 0,
"avgRating": 0,
"reviewCount": 0,
"seoScore": {
"score": 0,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "string",
"groupId": "string",
"variantId": "string",
"position": 0,
"minOverride": 0,
"maxOverride": 0,
"freeQuantityOverride": 0,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "string",
"internalName": "string",
"description": "string",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 0,
"freeQuantity": 0,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "string",
"modifiers": [
{
"id": "string",
"name": "string",
"description": "string",
"translations": {},
"priceDelta": "2.50",
"sku": "string",
"image": {},
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "string",
"status": "string"
}
]
}
]
}{
"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}/convert-to-simple"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/convert-to-simple"
}{
"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}/convert-to-simple"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/convert-to-simple"
}Get inventory for a product
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Product ID
curl -X GET "https://api.brainerce.com/api/v1/products/string/inventory"{
"id": "string",
"productId": "string",
"trackingMode": "TRACKED",
"backorderMode": "string",
"backorderLimit": 0,
"total": 0,
"reserved": 0,
"available": 0,
"lowStockThreshold": 0,
"lastInventorySyncAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/inventory"
}{
"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}/inventory"
}Update inventory for a product
Supports Idempotency-Key header for safe retries. ⛔ Rejects a KIT with 400: a kit holds no stock of its own — its availability is MIN(floor(component.available / quantity)), recomputed from its components on every read. Set the stock on the component products instead.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → 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 <= 255New absolute stock level
Variant ID (optional)
curl -X PUT "https://api.brainerce.com/api/v1/products/string/inventory" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "quantity": 0 }'{
"id": "string",
"productId": "string",
"trackingMode": "TRACKED",
"backorderMode": "string",
"backorderLimit": 0,
"total": 0,
"reserved": 0,
"available": 0,
"lowStockThreshold": 0,
"lastInventorySyncAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}{
"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}/inventory"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/inventory"
}{
"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}/inventory"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/inventory"
}Create a variant for a product
Create a variant under a VARIABLE product. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Parent 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 <= 255Stock keeping unit
GTIN (UPC/EAN/JAN/ISBN). Falls back to product GTIN when omitted.
Manufacturer part number. Falls back to product MPN when omitted.
length <= 70Variant display name
Variant attribute map
Empty Object
Variant price (in minor units of store currency)
Variant sale price
Variant unit cost (COGS) override, which falls back to the product costPrice for margin analytics. Merchant-internal: never exposed on public storefront responses.
Initial stock quantity for this variant
0 <= valueVariant-specific image (URL string or image object)
Empty Object
Position in variant list (0-indexed)
0 <= valueVariant status
"active" | "draft"Whether this variant is a digital download
Download payload for digital variants (files + limits)
Empty Object
Per-variant shipping class ID. null/undefined inherits from parent product.
Per-variant tax class ID. null/undefined inherits from product → category → store default.
curl -X POST "https://api.brainerce.com/api/v1/products/string/variants" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{}'{
"id": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}{
"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}/variants"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants"
}{
"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}/variants"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants"
}Bulk save variants
Create, update, and delete variants in a single transaction. Items with isDeleted: true are removed; items without id (or with an id starting with new-) are created; the rest are updated. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Parent 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 <= 255Variant inputs (create/update/delete in one call)
Optional product-level attribute links to set alongside the variants
curl -X POST "https://api.brainerce.com/api/v1/products/string/variants/bulk" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "variants": [ { "sku": "TSHIRT-S-RED", "attributes": { "size": "S", "color": "Red" }, "stock": 100, "isEnabled": true } ] }'{
"created": 2,
"updated": 3,
"deleted": 1,
"variants": [
{}
]
}{
"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}/variants/bulk"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/bulk"
}{
"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}/variants/bulk"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/bulk"
}Update a variant
Patch a single variant: only supplied fields are updated. This is how you set the real price of a VARIABLE product. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Parent product ID
Variant 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 <= 255Stock keeping unit
GTIN (UPC/EAN/JAN/ISBN). Falls back to product GTIN when omitted.
Manufacturer part number. Falls back to product MPN when omitted.
length <= 70Variant display name
Variant attribute map
Empty Object
Variant price (in minor units of store currency)
Variant sale price
Variant unit cost (COGS) override, which falls back to the product costPrice for margin analytics. Pass null to clear. Merchant-internal: never exposed on public storefront responses.
Variant-specific image (URL string or image object)
Empty Object
Position in variant list (0-indexed)
0 <= valueVariant status
"active" | "draft"Whether this variant is a digital download
Download payload for digital variants (files + limits)
Empty Object
Per-variant shipping class ID. null/undefined inherits from parent product.
Per-variant tax class ID. null clears it; resolution then falls back to product → category → store default.
Per-channel overrides keyed by vc_* connectionId. Value is a partial variant object; a nested translations[locale] object holds per-channel localized fields.
Empty Object
curl -X PATCH "https://api.brainerce.com/api/v1/products/string/variants/string" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{}'{
"id": "string",
"productId": "string",
"sku": "string",
"name": "string",
"attributes": {},
"price": "19.90",
"salePrice": "string",
"costPrice": "string",
"displayPrice": "string",
"displaySalePrice": "string",
"displayCurrency": "string",
"image": "string",
"position": 0,
"status": "string",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "string",
"name": "string",
"url": "string",
"storageKey": "string",
"size": 0,
"mimeType": "string"
}
],
"downloadLimit": 0,
"downloadExpiry": 0
},
"shippingClassId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}{
"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}/variants/{variantId}"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}"
}{
"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}/variants/{variantId}"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}"
}Delete a variant
Delete a variant by ID. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Parent product ID
Variant 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 <= 255curl -X DELETE "https://api.brainerce.com/api/v1/products/string/variants/string" \ -H "Idempotency-Key: string"{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}"
}{
"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}/variants/{variantId}"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}"
}Get variant inventory
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Parent product ID
Variant ID
curl -X GET "https://api.brainerce.com/api/v1/products/string/variants/string/inventory"{
"trackingMode": "TRACKED",
"total": 0,
"reserved": 0,
"available": 0,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}/inventory"
}{
"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}/variants/{variantId}/inventory"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}/inventory"
}Update variant inventory
Adjust a variant’s inventory. Provide newTotal to set absolute stock, trackingMode to switch tracking strategy, and optionally reason for the audit log. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → API Keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Path Parameters
Parent product ID
Variant 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 <= 255New absolute stock total. Must be a non-negative integer.
0 <= valueInventory tracking strategy for this variant
"TRACKED" | "UNLIMITED" | "DISABLED"Optional reason for the adjustment (recorded in the audit log)
curl -X PATCH "https://api.brainerce.com/api/v1/products/string/variants/string/inventory" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{}'{
"trackingMode": "TRACKED",
"total": 0,
"reserved": 0,
"available": 0,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
}{
"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}/variants/{variantId}/inventory"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}/inventory"
}{
"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}/variants/{variantId}/inventory"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/variants/{variantId}/inventory"
}Publish a product to a sales-channel site
Makes the product visible on the given sales channel (vibe-coded storefront). Accepts either the sales-channel record ID or its public vc_* connection ID. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → 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 <= 255Sales channel ID (record ID or vc_* connection ID)
curl -X POST "https://api.brainerce.com/api/v1/products/string/publish-sales-channel" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "salesChannelId": "string" }'{
"success": true,
"publish": {
"id": "clx1a2b3c4d5e6f7g8h9",
"salesChannelId": "string",
"storeId": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z",
"productId": "string"
},
"connectionName": "My storefront"
}{
"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}/publish-sales-channel"
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/publish-sales-channel"
}{
"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}/publish-sales-channel"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/publish-sales-channel"
}Unpublish a product from a sales-channel site
Hides the product from the given sales channel and clears its per-channel overrides. Accepts either the sales-channel record ID or its public vc_* connection ID. Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → Authentication → 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 <= 255Sales channel ID (record ID or vc_* connection ID)
curl -X POST "https://api.brainerce.com/api/v1/products/string/unpublish-sales-channel" \ -H "Idempotency-Key: string" \ -H "Content-Type: application/json" \ -d '{ "salesChannelId": "string" }'{
"success": true
}{
"statusCode": 401,
"code": "UNAUTHORIZED",
"message": "Invalid or expired credential",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/unpublish-sales-channel"
}{
"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}/unpublish-sales-channel"
}{
"statusCode": 404,
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products/{id}/unpublish-sales-channel"
}