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