{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Products"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"products","__idx":0},"children":["Products"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Applies to the Fulfilment API only."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A product is a SKU Quiqup can hold. Nothing else in fulfilment works until your products exist: an inbound references SKUs, inventory is keyed on SKU, and an order picks against them."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"creating-one","__idx":1},"children":["Creating one"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /api/fulfilment/products"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Four fields are required:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sku"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your stock keeping unit, 3 to 100 characters. Unique within your account. This is the key everything else uses."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["2 to 255 characters."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["selling_price"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["What the customer pays, greater than 0. A number, not a string."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["currency"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AED"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SAR"]},". Those are the only two."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A SKU that already exists on your account is rejected with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["already_exists"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Everything else is optional, but three groups of it matter operationally."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Physical."]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["weight"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["weight_unit"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["kg"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["g"]},") and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dimensions"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["length"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["width"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["height"]}," and a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["unit"]}," of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cm"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["m"]},"). The warehouse uses these for storage and the carrier uses them for shipping. Leave them out and the product is created with defaults: 0.5 kg, and 10 by 10 by 10 cm. Those defaults then reach the warehouse and the carrier, so send the real values."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Customs."]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["harmonized_system_code"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["country_code_of_origin"]}," (ISO2). Required in practice for anything shipping on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["partner_export"]},", not enforced at create."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Shelf life."]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batch_management"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["expiry_management"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["track_expiry"]}," switch on batch and expiry handling for the SKU. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inbound_expiry_threshold_days"]}," is the minimum shelf life the warehouse will accept when receiving stock; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["outbound_expiry_threshold_days"]}," is the minimum it will pick with. Set these before the first inbound, not after."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Also available: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["barcode"]}," (defaults to the SKU), ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["description"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["vendor"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["image"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]}," (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["physical"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["digital"]},"), ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cost_price"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["platform_price"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["partner_product_id"]}," (defaults to the SKU) and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["active"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["archived"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["draft"]},", and defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["active"]},". Archiving is how you retire a SKU. There is no delete."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["availability_status"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inventory_tracked"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requires_shipping"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["total_sales"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["retail_price"]}," are accepted and ignored. Stock comes from ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/fulfilment/inventory"},"children":["inventory"]},", not from the product."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"reading-and-updating","__idx":2},"children":["Reading and updating"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /api/fulfilment/products/{sku}"]}," takes the SKU. The product ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["id"]}," is not accepted in its place."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PATCH /api/fulfilment/products/{sku}"]}," is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not a partial update"]},". The body is the whole product and replaces what is stored, so:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Send ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sku"]}," (the same as in the path), ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["selling_price"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["currency"]}," every time."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Most fields you leave out are reset, not kept. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["description"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["vendor"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["image"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["harmonized_system_code"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["country_code_of_origin"]}," are cleared. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["barcode"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["partner_product_id"]}," go back to the SKU. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["weight"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["weight_unit"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dimensions"]}," go back to the defaults above. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," goes back to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["active"]},", so a PATCH that leaves ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," out un-archives the SKU."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["These are kept when you leave them out: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cost_price"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["platform_price"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["track_expiry"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batch_management"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["expiry_management"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inbound_expiry_threshold_days"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["outbound_expiry_threshold_days"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To change one field, read the product, change that field, and send the whole product back. One exception: when no cost is stored, a read returns the selling price as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cost_price"]},", so leave ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["cost_price"]}," out rather than copying it back."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To remove a stored ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["platform_price"]},", send ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clear_platform_price: true"]},". Leaving ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["platform_price"]}," out keeps it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"bulk-upload","__idx":3},"children":["Bulk upload"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Two calls, in this order, both taking a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["multipart/form-data"]}," body with the file in a part named ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["file"]},"."]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /api/fulfilment/products/bulk/validate"]}," parses the file and returns a per-row status: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["valid"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["invalid"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["duplicate"]}," (the SKU already exists on your account) or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["duplicate_in_file"]},". Nothing is written."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /api/fulfilment/products/bulk/commit"]}," parses, validates and writes. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["update_duplicates"]}," form field decides what happens to an existing SKU: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["true"]}," updates it, anything else skips it. Each row comes back ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["created"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["updated"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["skipped"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["failed"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The file is a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".csv"]}," (comma or semicolon separated) or an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".xlsx"]}," workbook, of which only the first sheet is read. At most 10 MB and 5,000 data rows. The first row holds the column names, and eight columns are required: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sku"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["selling_price"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["currency"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["barcode"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hs_code"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["batch_management"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["expiry_management"]},". The column names of Quiqup's product template are accepted too."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A file that cannot be read at all comes back ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}," with a body of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"error\": \"...\"}"]},", not the usual ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/errors"},"children":["error envelope"]},". A failure on Quiqup's side while processing the file comes back ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["500"]}," with the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"error\": \"...\"}"]}," body."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Commit is not atomic."]}," Rows are processed one at a time, so a failure part way through leaves earlier rows written and later ones not. The response carries the status of every row. Read it and retry the failures individually rather than resubmitting the file."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validate first. It costs one call and it is the only way to see the whole file's problems before any of it lands."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-is-not-here","__idx":4},"children":["What is not here"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There is no delete, no list-all-products endpoint, and no way to create a product and its opening stock in one call. Stock arrives through an ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/fulfilment/inbound"},"children":["inbound"]},"."]}]},"headings":[{"value":"Products","id":"products","depth":1},{"value":"Creating one","id":"creating-one","depth":2},{"value":"Reading and updating","id":"reading-and-updating","depth":2},{"value":"Bulk upload","id":"bulk-upload","depth":2},{"value":"What is not here","id":"what-is-not-here","depth":2}],"frontmatter":{"seo":{"title":"Products"}},"lastModified":"2026-10-16T10:47:34.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/fulfilment/products","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}