Create a new product
Supports Idempotency-Key header for safe retries.
Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.
In: header
Header Parameters
Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.
- Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
- Reusing a key with a different body returns
409 Conflictwithcode: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response. - Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
- Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
- Sending this header on a
GETreturns400withcode: "IDEMPOTENCY_KEY_NOT_SUPPORTED"— GETs are already idempotent. - Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.
length <= 255Display name of the product. Required, 1 to 500 characters.
length <= 500URL slug. Letters, numbers, and hyphens only. Auto-generated from name when omitted.
^[\p{L}\p{N}-]+$length <= 500Stock keeping unit, meaning your internal product code.
length <= 191Global Trade Item Number (universal barcode: EAN/UPC/ISBN). Required by Google Shopping, Facebook Catalog, and TikTok Shop on most categories. Validated against the checksum digit.
Manufacturer Part Number, paired with brand when no GTIN exists. Max 70 chars.
length <= 70Your own stable identifier for this product in the SOURCE system (supplier feed, legacy store, ERP). Unique per store. Its purpose is safe retries on bulk imports: re-sending a batch after a timeout matches on this value and skips the product instead of creating a duplicate. Not a marketplace id: those are managed per-connection and are not settable here.
length <= 191Sale-price effective window start (ISO 8601). Enforced: before the start and after salePriceEndsAt the storefront, cart and checkout use the regular price. Variants without dates of their own follow this window. Also the Google Merchant Center sale_price_effective_date. Both this and salePriceEndsAt must be set together, or neither applies.
Sale-price effective window end (ISO 8601). See salePriceStartsAt.
Shipping weight numeric value. Paired with shippingWeightUnit.
0 <= valueShipping weight unit.
"kg" | "lb" | "g" | "oz"Shipping package length. Paired with shippingDimensionUnit.
0 <= valueShipping package width. Paired with shippingDimensionUnit.
0 <= valueShipping package height. Paired with shippingDimensionUnit.
0 <= valueUnit shared by shippingLengthValue/shippingWidthValue/shippingHeightValue.
"cm" | "in"SEO meta description (≤160 chars). Used by Google Search snippets, social link previews (WhatsApp/Telegram/Twitter), email previews, and schema.org JSON-LD. Falls back to a stripped/truncated description when omitted.
length <= 160Long-form product description. Accepts HTML, and the backend strips dangerous tags/attributes (<script>, inline event handlers, <iframe>, …) before saving. Hard cap of 65 535 characters.
length <= 65535How description is written. Omit it and the platform decides: a description that contains real HTML tags is stored as html, anything else keeps its current format (text for a new product). Send it to override that, for example text for a description that only quotes a tag. A description marked html is sanitized on write and rendered as HTML by the storefront; the SEO audit does not penalize it.
"text" | "html" | "markdown"Base list price in the store currency. Must be ≥ 0.
0 <= valueSale price: greater than 0 and lower than basePrice, otherwise the request is refused with a 400 naming the field (a sale price is never silently dropped). When present, the storefront displays it as the active price with basePrice shown struck-through. Omit it for a product that is not on sale.
0 <= valueInternal cost (what you pay your supplier). Used for margin reporting only, and never shown to shoppers.
0 <= valuePublish state. active = visible to shoppers; draft = only visible in the dashboard.
"active" | "draft"Product structure. SIMPLE = single SKU; VARIABLE = parent of multiple variants (size, color, …); KIT = a bundle priced from component products. A KIT may be created with no components yet — it is simply not purchasable until it has at least one.
"SIMPLE" | "VARIABLE" | "KIT"How a KIT is priced. FIXED (default) — you set basePrice and it stays put. SUM — the kit costs exactly what its components cost, recomputed on every read, so a component going on sale lowers the kit price on its own. SUM_MINUS_PERCENT — that sum less kitDiscountValue percent. Ignored on SIMPLE and VARIABLE products.
"FIXED""FIXED" | "SUM" | "SUM_MINUS_PERCENT"Percent off the component sum, 0-100. Required when kitPricingMode is SUM_MINUS_PERCENT; ignored otherwise.
0 <= value <= 100If true, the product is a digital download, so checkout issues a signed URL after payment instead of triggering shipping.
Digital-download configuration. Required when isDownloadable=true. files[] are uploaded via /v1/media. downloadLimit = max downloads per order (null = unlimited). downloadExpiry = hours until the signed URL expires (null = no expiry).
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.
Array of variants. Required (and non-empty) for VARIABLE products, at most 500. Each variant carries its own price, SKU, and inventory.
items <= 500Array 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.
items <= 100Category 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.
items <= 100Array 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.
items <= 50Brand 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.
items <= 50Free-form tags for filtering and segmentation.
items <= 250Product images. Each entry is an image URL string or an object { url, position, isMain }. External URLs are imported into your media library; to reuse a library asset, send its URL and key from /v1/media.
items <= 250Where to publish this product: platform codes of installed connector apps (for example google-merchant-center), or vc:<salesChannelId> for one of your sales channels. Each platform syncs the product separately and can have per-channel overrides (see channels). SHOPIFY and WOOCOMMERCE are retired: they are still accepted and ignored.
items <= 50Per-platform field overrides. Keyed by platform code, value is a partial product object that overrides the canonical fields for that channel only.
Tax category. taxable is the default; exempt skips all tax calculation.
"taxable""taxable" | "exempt"Multi-language overrides. Keyed by ISO locale code (he, ar, es, …). Each value is a partial product with translated name, description, seoTitle, seoDescription and slug. Falls back to the canonical fields for missing locales.
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": "clx1a2b3c4d5e6f7g8h9",
"name": "Classic T-Shirt",
"slug": "classic-t-shirt",
"localeSlugs": {},
"description": "A soft cotton t-shirt.",
"descriptionFormat": "text",
"basePrice": "49.90",
"salePrice": "24.90",
"costPrice": "12.00",
"priceMin": "24.90",
"priceMax": "34.90",
"priceVaries": true,
"displayPrice": "29.90",
"displaySalePrice": "24.90",
"displayPriceMin": "24.90",
"displayPriceMax": "34.90",
"displayCurrency": "USD",
"gtin": "0123456789012",
"mpn": "TS-BLK-M",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"shippingWeightValue": 0.3,
"shippingWeightUnit": "kg",
"shippingLengthValue": 30,
"shippingWidthValue": 20,
"shippingHeightValue": 5,
"shippingDimensionUnit": "cm",
"metaDescription": "Soft cotton t-shirt in five colors",
"status": "active",
"sku": "TSHIRT-BLK-M",
"type": "SIMPLE",
"kitPricingMode": "FIXED",
"kitDiscountValue": 10,
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "photos.zip",
"url": "https://cdn.example.com/downloads/photos.zip",
"storageKey": "downloads/photos.zip",
"size": 5242880,
"mimeType": "application/zip"
}
],
"downloadLimit": 3,
"downloadExpiry": 30
},
"needsSync": true,
"lastSyncedAt": "2019-08-24T14:15:22Z",
"menuOrder": 0,
"taxBehavior": "taxable",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"variants": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"productId": "clx1a2b3c4d5e6f7g8h9",
"sku": "TSHIRT-BLK-M",
"name": "Black / M",
"attributes": {
"size": "S",
"color": "Red"
},
"price": "19.90",
"salePrice": "24.90",
"costPrice": "12.00",
"salePriceStartsAt": "2019-08-24T14:15:22Z",
"salePriceEndsAt": "2019-08-24T14:15:22Z",
"displayPrice": "29.90",
"displaySalePrice": "24.90",
"displayCurrency": "USD",
"image": "string",
"position": 0,
"status": "active",
"inventory": {
"trackingMode": "TRACKED",
"total": 42,
"reserved": 3,
"available": 39,
"inStock": true,
"canPurchase": true,
"lastInventorySyncAt": "2019-08-24T14:15:22Z"
},
"isDownloadable": true,
"downloadData": {
"files": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "photos.zip",
"url": "https://cdn.example.com/downloads/photos.zip",
"storageKey": "downloads/photos.zip",
"size": 5242880,
"mimeType": "application/zip"
}
],
"downloadLimit": 3,
"downloadExpiry": 30
},
"shippingClassId": "clx1a2b3c4d5e6f7g8h9",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"images": [
{
"url": "https://cdn.example.com/products/tshirt.jpg",
"position": 0,
"isMain": true,
"alt": "Black t-shirt on a hanger",
"id": "clx1a2b3c4d5e6f7g8h9",
"key": "products/tshirt.jpg",
"thumbnailUrl": "https://cdn.example.com/products/tshirt-thumb.jpg",
"width": 1200,
"height": 800,
"size": 245123,
"mimeType": "image/jpeg",
"createdAt": "2019-08-24T14:15:22Z",
"source": "local",
"importStatus": "completed",
"importError": "HTTP 404 from the source server",
"src": "https://cdn.example.com/products/tshirt.jpg",
"altSource": "asset",
"altTranslations": {},
"originalFilename": "tshirt.jpg",
"sourceUrl": "https://source.example.com/img/tshirt.jpg",
"sourcePlatform": "SHOPIFY",
"importedAt": "2026-01-15T10:30:00.000Z",
"platformImageIds": {}
}
],
"channels": {},
"translations": {},
"categories": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Shoes",
"taxBehavior": "taxable"
}
],
"brands": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"attributes": [
"string"
],
"productAttributeOptions": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"attributeId": "clx1a2b3c4d5e6f7g8h9",
"attributeOptionId": "clx1a2b3c4d5e6f7g8h9",
"platform": "GLOBAL",
"attribute": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Color",
"displayType": "COLOR_SWATCH"
},
"attributeOption": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Black",
"value": "black",
"swatchColor": "#000000",
"swatchColor2": "#FFFFFF",
"swatchImageUrl": "https://cdn.example.com/swatches/black.png"
}
}
],
"tags": [
"string"
],
"metafields": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"definitionId": "clx1a2b3c4d5e6f7g8h9",
"definitionKey": "material",
"definitionName": "Material",
"type": "TEXT",
"value": "Cotton",
"variantId": "clx1a2b3c4d5e6f7g8h9"
}
],
"customizationFields": [
{
"definitionId": "clx1a2b3c4d5e6f7g8h9",
"name": "Engraving text",
"key": "engraving",
"description": "Up to 20 characters",
"type": "TEXT",
"required": true,
"minLength": 1,
"maxLength": 20,
"minValue": 0,
"maxValue": 100,
"enumValues": [
"string"
],
"defaultValue": "Happy birthday",
"position": 0
}
],
"channelPublishes": [
{
"salesChannel": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
},
"connection": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
}
}
],
"vibeCodedPublishes": [
{
"salesChannel": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
},
"connection": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "My storefront",
"connectionId": "vc_9f3a2b1c8d7e6f5a"
}
}
],
"channelSync": {},
"shippingClassId": "clx1a2b3c4d5e6f7g8h9",
"shippingClass": {
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Heavy items"
},
"crossSells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"upsells": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"related": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Summer Collection"
}
],
"bundleOfferCount": 1,
"orderBumpCount": 1,
"avgRating": 4.6,
"reviewCount": 18,
"seoScore": {
"score": 82,
"band": "excellent",
"hasUnresolvedError": true
},
"modifierGroups": [
{
"attachmentId": "clx1a2b3c4d5e6f7g8h9",
"groupId": "clx1a2b3c4d5e6f7g8h9",
"variantId": "clx1a2b3c4d5e6f7g8h9",
"position": 0,
"minOverride": 0,
"maxOverride": 3,
"freeQuantityOverride": 1,
"requiredOverride": true,
"freeAllocationPolicyOverride": "EXPENSIVE_FREE",
"defaultModifierIds": [
"string"
],
"name": "Toppings",
"internalName": "toppings-pizza",
"description": "Choose up to 3",
"translations": {},
"selectionType": "SINGLE",
"minSelections": 0,
"maxSelections": 3,
"freeQuantity": 1,
"required": true,
"freeAllocationPolicy": "EXPENSIVE_FREE",
"status": "active",
"modifiers": [
{
"id": "clx1a2b3c4d5e6f7g8h9",
"name": "Extra cheese",
"description": "Double portion",
"translations": {},
"priceDelta": "2.50",
"sku": "TSHIRT-BLK-M",
"image": "http://example.com",
"position": 0,
"isDefault": true,
"available": true,
"referencedProductId": "clx1a2b3c4d5e6f7g8h9",
"status": "active"
}
]
}
]
}{
"statusCode": 400,
"code": "VALIDATION_FAILED",
"message": "name should not be empty, price must be a positive number",
"timestamp": "2026-08-23T10:15:00.000Z",
"path": "/api/v1/products"
}{
"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/store"
}