These endpoints cover a product's write lifecycle: creation, update (partial or full), and deletion. SKU renaming has no endpoint of its own — it's triggered implicitly on update. Deletion is permanent.
Required permissions:products:create to create, products:update to update, products:delete to delete.
Maximum 100 characters. Unique per tenant (composite index tenantId + sku).
title.value
string
Maximum 500 characters.
price
number
Minimum 0.
currency
string
ISO 4217 code.
status
string
active, disabled, or draft — see the note on the real enum below.
The real status has 3 values, not 5
The persisted (Mongoose) schema only accepts active, disabled, and draft, with default draft. If your integration comes from another source that assumes the 5 values of the public TS type (active/disabled/inactive/draft/out_of_stock), be careful: inactive and out_of_stock are rejected on save.
A legacy variant also exists:PUT /products (without {id} in the route, with the SKU inside the body). Same permission and same behavior — documented here for completeness, but use PUT /products/{id} for new integrations.
To change a product's SKU, send the new value in the sku field of PUT /products/{id}'s body. If body.sku differs from the stored SKU, the lambda internally triggers a cascading rename — there is no separate route like POST /products/{id}/rename.
The cascading rename updates, in the same operation: the associated inventory records, inventory_tracks, references from BOM/bundle/modifiers that point to the old SKU, and it emits the ProductSkuChanged event. Treat it as a higher-impact operation than a simple field update — verify the new SKU doesn't collide with an existing one (it will respond 409 duplicate-sku if it does).
{ "code": "not-found", "message": "Product not found" }
Permission required:products:delete
Irreversible hard delete — no trash, no soft-delete
This endpoint executes a direct deleteOne against the MongoDB collection. There is no recycle bin nor a recoverable state. Once deleted, the product and its history stop existing in the database — you cannot restore it from the API.
This is different from setting status: 'disabled', which is a reversible logical state (the product still exists, it's just hidden/deactivated). If your flow needs the ability to "undo", use PUT /products/{id} with {"status": "disabled"} instead of DELETE.