Update Product
PUT/api/products/:id
Update an existing product by ID. All fields are optional on update.
products:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Product type cannot be changed if inventory movements exist (exception: standard -> kit is allowed). Additionally, an inventory-bearing product (standard/kit/blemished/manufactured) cannot be converted to a non-inventory type (bundle/matrix) while it still carries any inventory ledger - a FIFO layer OR an inventory movement of any type (including stock_take, receipt, adjustment, transfer). This prevents orphaning stock layers and movements.
For update (PUT), additional field available:
- initial_inventory.warehouses[].id (required with initial_inventory)
- initial_inventory.warehouses[].quantity (required)
- initial_inventory.warehouses[0].unit_cost (required)
- images[].id — required only when images[].operation is delete
- images[].operation — nullable|in:update_create,delete
Optional field: mpn (string, max 255, nullable) — manufacturer part number.
Optional field: hts_code (string, max 255, nullable) — Harmonized Tariff Schedule code.
Optional field: product_type_id (integer, nullable) — the product's product type (Organization → Product Types); must be an active leaf type. It decides which channel category the product publishes into on each sales channel. Send null to clear it.
Returns 422 if product type change is invalid.
Bundle / Kit components: When the resulting product type is bundle or kit, components[] must be present and non-empty. This applies whether you are (a) changing the type from standard → bundle|kit, (b) updating an existing bundle/kit that currently has no components, or (c) sending an empty components: [] payload (which would otherwise wipe components). Returns a 422 validation error on the components field in these cases. Updates that don't touch components on a bundle/kit which already has components are unaffected. To convert a standard product that has no components yet, supply components[] in the same request as the type change — the components are saved as part of the same update, satisfying the requirement atomically.
Note (pricing tiers): When sending suppliers[].pricing[] entries with operation: updateOrCreate (or omitted), price is required and must be a numeric value >= 0 and < 100000. To remove a tier, send operation: "delete". Sending price: null will return a 422 validation error on suppliers.{i}.pricing.{j}.price.
Lot tracking fields (optional):
is_lot_tracked(boolean, nullable) — enable batch/lot tracking for this product.lot_tracking_method(string, nullable) — required whenis_lot_trackedis true. Allowed values:fifo(First-In, First-Out),fefo(First-Expiry, First-Out).
Stock unit of measure (optional):
stock_uom_id(integer, nullable) — id of a row inunits_of_measure. When omitted, the product is auto-assignedEACHas its stock UoM on create. PickKG,L, etc. for bulk weight or volume products so quantities, purchase orders, sales orders, and stock takes use the correct unit from day one.
Also accepts expiration_offset_months (integer 0-120, nullable) — the product's default FBA-inbound shelf-life estimate in months; blank uses the app default (10). Responses include it too.
Supplier links (suppliers[]): Supplier changes apply to every product type (standard, kit, bundle, matrix, blemished, manufactured). Each entry accepts an optional operation field:
updateOrCreate(default when omitted) — link the supplier to the product, creating the link if it does not exist and updatingsupplier_sku,leadtime,minimum_order_quantity,is_default, andpricing[]if it does.delete— unlink the supplier from the product. Onlysupplier_idis required; the supplier's pricing tiers and per-warehouse supplier inventory for this product are removed with it.
When no entry carries an operation, suppliers[] is treated as the complete list and any supplier absent from it is unlinked. When any entry carries an operation, only the listed suppliers are touched and omitted suppliers are left alone — so an incremental edit should set operation on every entry it sends.
allow_negative_inventory (boolean|null, optional): whether this product may ship when it would take a warehouse below zero on hand. Deliberately one-way — false pins the product to refusing, whatever the warehouse or company setting says, and is the way to protect an item that must never oversell. true grants nothing on its own: a product cannot unlock permission the warehouse or company has not already given. Omit or send null to inherit.
Serial format. serial_format_override, when set, must include a sequence token, {seq} or {seq:N}, so every generated serial is different. A format without one fails with 422 on serial_format_override.
B2B Portal ordering rules (optional; honoured by the buyer portal's catalog, cart and inventory feed):
portal_visible(boolean) — show the product on the portal. Defaulttrue.min_order_quantity(integer >= 1, nullable) — product-level MOQ.null= no minimum.order_increment(integer >= 1, nullable) — buyers order in multiples of this.nullfalls back tocase_quantity, else any quantity.allow_backorder(boolean, nullable) — per-product backorder override.nullinherits the account's, then the portal's default.portal_lead_time_days(integer >= 0, nullable) — lead time shown to buyers when the product is out of stock.portal_exclude_from_order_discount(boolean) — net price: the product still counts toward an account's order-value discount threshold ("spend $X, take Y% off") but never takes the discount. Defaultfalse.
Converting a product to a bundle: order lines that already sell the product as a single item are split into the bundle's components in the same request, as long as they hold nothing yet. If an open line already holds a reservation, a fulfillment or a stock movement for the product, the conversion is refused with a 422 that names those orders. Unallocate, ship or cancel those lines first.
sku cannot be cleared (an empty or null SKU returns 422). is_dropshippable and is_lot_tracked may be omitted but cannot be null. Sending brand_name: null (or an empty string) removes the product's brand; omitting brand_name leaves it unchanged.
Pricing tier must be one the supplier uses: each suppliers[].pricing[].supplier_pricing_tier_id must be a tier attached to that supplier (or, when the supplier has no tiers attached, the account's default tier). Otherwise the request fails with 422 on suppliers.{i}.pricing.{j}.supplier_pricing_tier_id, because the price would never show on the supplier. Attach the tier to the supplier first, or choose one of the supplier's tiers. Entries sent with operation: "delete" are always accepted, so a price saved on such a tier earlier can still be removed.
Request
Responses
- 200
- 401
- 403
- 404
- 422
- 429
OK
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Not found — no record with the given identifier (or the route does not exist). Verify the ID before retrying.
Unprocessable Entity
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.