API ReferenceEndpointsProducts

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

POST
/vc/{connectionId}/products/{productId}/review-photo
X-Sales-Channel-Origin<token>

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

connectionIdstring
productIdstring

Header Parameters

originstring
authorizationstring
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

GET
/v1/products
AuthorizationBearer <token>

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

page?number
limit?number
search?string
status?string
type?string
sortBy?string
sortOrder?string
regionId?string
curl -X GET "https://api.brainerce.com/api/v1/products?page=0&limit=0&search=string&status=string&type=string&sortBy=string&sortOrder=string&regionId=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.

POST
/v1/products
AuthorizationBearer <token>

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

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
namestring

Display name of the product. Required.

slug?string

URL slug. Letters, numbers, and hyphens only. Auto-generated from name when omitted.

sku?string

Stock keeping unit, meaning your internal product code.

gtin?string

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.

mpn?string

Manufacturer Part Number, paired with brand when no GTIN exists. Max 70 chars.

externalId?string

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.

Lengthlength <= 191
salePriceStartsAt?string

Sale-price effective window start (ISO 8601). Google Merchant Center sale_price_effective_date. Both this and salePriceEndsAt must be set together, or neither applies.

salePriceEndsAt?string

Sale-price effective window end (ISO 8601). See salePriceStartsAt.

shippingWeightValue?number

Shipping weight numeric value. Paired with shippingWeightUnit.

shippingWeightUnit?string

Shipping weight unit.

Value in"kg" | "lb" | "g" | "oz"
shippingLengthValue?number

Shipping package length. Paired with shippingDimensionUnit.

shippingWidthValue?number

Shipping package width. Paired with shippingDimensionUnit.

shippingHeightValue?number

Shipping package height. Paired with shippingDimensionUnit.

shippingDimensionUnit?string

Unit shared by shippingLengthValue/shippingWidthValue/shippingHeightValue.

Value in"cm" | "in"
metaDescription?string

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.

Lengthlength <= 160
description?string

Long-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.

Lengthlength <= 65535
basePricenumber

Base list price in the store currency. Must be ≥ 0.

Range0 <= value
salePrice?number

Sale price (must be < basePrice). When present, the storefront displays it as the active price with basePrice shown struck-through.

Range0 <= value
costPrice?number

Internal cost (what you pay your supplier). Used for margin reporting only, and never shown to shoppers.

Range0 <= value
status?string

Publish state. active = visible to shoppers; draft = only visible in the dashboard.

Value in"active" | "draft"
typestring

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.

Value in"SIMPLE" | "VARIABLE" | "KIT"
kitPricingMode?string

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.

Value in"FIXED" | "SUM" | "SUM_MINUS_PERCENT"
kitDiscountValue?number

Percent off the component sum, 0-100. Required when kitPricingMode is SUM_MINUS_PERCENT; ignored otherwise.

Range0 <= value <= 100
isDownloadable?boolean

If true, the product is a digital download, so checkout issues a signed URL after payment instead of triggering shipping.

downloadData?object

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?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

variants?array<CreateVariantDto>

Array of variants. Required (and non-empty) for VARIABLE products, at most 500. Each variant carries its own price, SKU, and inventory.

categories?array<string>

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.

categoryNames?array<string>

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.

brands?array<string>

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.

brandNames?array<string>

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.

tags?array<string>

Free-form tags for filtering and segmentation.

images?array<string>

Product images. Each entry: { url, position, isMain }. Upload via /v1/media first to get a signed URL.

platforms?array<string>

Sales channels this product should publish to. Each platform syncs the product separately and can have per-channel overrides (see channels).

channels?object

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

taxBehavior?string

Tax category. taxable is the default; exempt skips all tax calculation.

Value in"taxable" | "exempt"
translations?object

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

GET
/v1/products/{id}
AuthorizationBearer <token>

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

idstring

Product ID

Query Parameters

regionId?string
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.

PATCH
/v1/products/{id}
AuthorizationBearer <token>

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

idstring

Product ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
name?string

Display name of the product.

slug?string

URL slug. Letters, numbers, and hyphens only. Changing the slug breaks existing shopper bookmarks for this product.

sku?string

Stock keeping unit, meaning your internal product code.

gtin?string

Global Trade Item Number (universal barcode: EAN/UPC/ISBN). Validated against the checksum digit. Pass an empty string to clear.

mpn?string

Manufacturer Part Number, paired with brand when no GTIN exists. Max 70 chars.

Lengthlength <= 70
salePriceStartsAt?string

Sale-price effective window start (ISO 8601). Pass null to clear.

salePriceEndsAt?string

Sale-price effective window end (ISO 8601). Pass null to clear.

shippingWeightValue?number

Shipping weight numeric value. Pass null to clear.

shippingWeightUnit?string

Shipping weight unit.

Value in"kg" | "lb" | "g" | "oz"
shippingLengthValue?number

Shipping package length. Pass null to clear.

shippingWidthValue?number

Shipping package width. Pass null to clear.

shippingHeightValue?number

Shipping package height. Pass null to clear.

shippingDimensionUnit?string

Unit shared by the dimension fields.

Value in"cm" | "in"
metaDescription?string

SEO meta description (≤160 chars). Used by Google Search snippets and social link previews. Falls back to a stripped/truncated description when omitted.

Lengthlength <= 160
description?string

Long-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.

Lengthlength <= 65535
basePrice?number

Base list price in the store currency.

Range0 <= value
salePrice?number | null

Sale 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).

Range0 <= value
costPrice?number | null

Internal cost (what you pay your supplier). Used for margin reporting only, and never shown to shoppers. Pass null to clear.

status?string

Publish state. active = visible to shoppers; draft = only visible in the dashboard.

Value in"active" | "draft"
type?string

Product structure. SIMPLE = single SKU; VARIABLE = parent of multiple variants.

Value in"SIMPLE" | "VARIABLE" | "KIT"
kitPricingMode?string

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.

Value in"FIXED" | "SUM" | "SUM_MINUS_PERCENT"
kitDiscountValue?number

Percent off the component sum, 0-100. Required when kitPricingMode is SUM_MINUS_PERCENT; ignored otherwise.

Range0 <= value <= 100
isDownloadable?boolean

If true, the product is a digital download, so checkout issues a signed URL after payment instead of triggering shipping.

needsSync?boolean

When true, the product is flagged for re-sync to connected platforms on the next sync job.

channels?object

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

tags?array<string>

Free-form tags for filtering and segmentation.

categories?array<string>

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.

categoryNames?array<string>

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).

brands?array<string>

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.

brandNames?array<string>

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).

images?array<string>

Product images. Each entry: { url, position, isMain }. Upload via /v1/media first to get a signed URL.

menuOrder?number

Display order within the catalog/category listing. Lower values render first. Mirrors WooCommerce menu_order.

shippingClassId?string | null

ID of the shipping class this product belongs to (used by shipping-rate rules). Pass null to clear.

taxClassId?string | null

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).

downloadData?object

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

taxBehavior?string

Tax category. taxable is the default; exempt skips all tax calculation.

Value in"taxable" | "exempt"
translations?object

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.

DELETE
/v1/products/{id}
AuthorizationBearer <token>

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

idstring

Product ID

Query Parameters

platforms?string

Platforms to delete from (comma-separated)

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
curl -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.

POST
/v1/products/bulk
AuthorizationBearer <token>

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

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
productsarray<CreateProductDto>

Products 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.

importId?string

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.

Lengthlength <= 191
conflictStrategy?string

How 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.

Value in"skip" | "error"
syncMode?string

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.

Value in"coalesced" | "none"
idempotencyKey?string

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.

Lengthlength <= 191
curl -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.

GET
/v1/products/bulk/{jobId}
AuthorizationBearer <token>

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

jobIdstring

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.

GET
/v1/products/bulk/import/{importId}
AuthorizationBearer <token>

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

importIdstring

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.

GET
/v1/products/bulk/{jobId}/errors
AuthorizationBearer <token>

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

jobIdstring

Job ID returned by POST /v1/products/bulk

Query Parameters

page?string

Page number (default 1)

limit?string

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.

PATCH
/v1/products/{id}/convert-to-variable
AuthorizationBearer <token>

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

idstring

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.

PATCH
/v1/products/{id}/convert-to-simple
AuthorizationBearer <token>

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

idstring

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

GET
/v1/products/{id}/inventory
AuthorizationBearer <token>

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

idstring

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.

PUT
/v1/products/{id}/inventory
AuthorizationBearer <token>

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

idstring

Product ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
quantitynumber

New absolute stock level

variantId?string

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.

POST
/v1/products/{id}/variants
AuthorizationBearer <token>

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

idstring

Parent product ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
sku?string

Stock keeping unit

gtin?string

GTIN (UPC/EAN/JAN/ISBN). Falls back to product GTIN when omitted.

mpn?string

Manufacturer part number. Falls back to product MPN when omitted.

Lengthlength <= 70
name?string

Variant display name

attributes?object

Variant attribute map

Empty Object

price?number

Variant price (in minor units of store currency)

salePrice?number

Variant sale price

costPrice?number

Variant unit cost (COGS) override, which falls back to the product costPrice for margin analytics. Merchant-internal: never exposed on public storefront responses.

inventory?number

Initial stock quantity for this variant

Range0 <= value
image?object

Variant-specific image (URL string or image object)

Empty Object

position?number

Position in variant list (0-indexed)

Range0 <= value
status?string

Variant status

Value in"active" | "draft"
isDownloadable?boolean

Whether this variant is a digital download

downloadData?object

Download payload for digital variants (files + limits)

Empty Object

shippingClassId?string | null

Per-variant shipping class ID. null/undefined inherits from parent product.

taxClassId?string | null

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.

POST
/v1/products/{id}/variants/bulk
AuthorizationBearer <token>

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

idstring

Parent product ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
variantsarray<BulkVariantInputDto>

Variant inputs (create/update/delete in one call)

productAttributes?array<ProductAttributeInputDto>

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.

PATCH
/v1/products/{id}/variants/{variantId}
AuthorizationBearer <token>

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

idstring

Parent product ID

variantIdstring

Variant ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
sku?string

Stock keeping unit

gtin?string

GTIN (UPC/EAN/JAN/ISBN). Falls back to product GTIN when omitted.

mpn?string

Manufacturer part number. Falls back to product MPN when omitted.

Lengthlength <= 70
name?string

Variant display name

attributes?object

Variant attribute map

Empty Object

price?number

Variant price (in minor units of store currency)

salePrice?number

Variant sale price

costPrice?number | null

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.

image?object

Variant-specific image (URL string or image object)

Empty Object

position?number

Position in variant list (0-indexed)

Range0 <= value
status?string

Variant status

Value in"active" | "draft"
isDownloadable?boolean

Whether this variant is a digital download

downloadData?object

Download payload for digital variants (files + limits)

Empty Object

shippingClassId?string | null

Per-variant shipping class ID. null/undefined inherits from parent product.

taxClassId?string | null

Per-variant tax class ID. null clears it; resolution then falls back to product → category → store default.

channels?object

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.

DELETE
/v1/products/{id}/variants/{variantId}
AuthorizationBearer <token>

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

idstring

Parent product ID

variantIdstring

Variant ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
curl -X DELETE "https://api.brainerce.com/api/v1/products/string/variants/string" \  -H "Idempotency-Key: string"
Empty

{
  "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

GET
/v1/products/{id}/variants/{variantId}/inventory
AuthorizationBearer <token>

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

idstring

Parent product ID

variantIdstring

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.

PATCH
/v1/products/{id}/variants/{variantId}/inventory
AuthorizationBearer <token>

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

idstring

Parent product ID

variantIdstring

Variant ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
newTotal?number

New absolute stock total. Must be a non-negative integer.

Range0 <= value
trackingMode?string

Inventory tracking strategy for this variant

Value in"TRACKED" | "UNLIMITED" | "DISABLED"
reason?string

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.

POST
/v1/products/{id}/publish-sales-channel
AuthorizationBearer <token>

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

idstring

Product ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
salesChannelIdstring

Sales 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.

POST
/v1/products/{id}/unpublish-sales-channel
AuthorizationBearer <token>

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

idstring

Product ID

Header Parameters

Idempotency-Key?string

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 Conflict with code: "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 GET returns 400 with code: "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.

See /docs/api/idempotency.

Lengthlength <= 255
salesChannelIdstring

Sales 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"
}

On this page

No Headings