Release 2026.09-R1

Updated September 28, 20266 min read

Released September 2, 2026.

Inventory keys

Stock is now held against an inventory key (inventory_key), not against a product ID. A product carries the key of the stock it sells from. Products that share a key share the same stock, at every location. Each key has one inventory position per location.

A product without an inventory key has no stock tracked. Order allocation and delivery options ignore stock for it.

  • In the Dashboard: products have an Inventory Key field under Settings > Administration > Products. You can set it on the product page, sort and export the product list by it, and include it in product imports. The inventory import file now needs an Inventory Key column in place of Product ID.
  • Via the API: products take and return inventory_key. Once a product has a key, an update cannot clear it. Order line items return the inventory_key of their product.
  • Fulfillment App: receiving a return updates stock through the product's inventory key. Items whose product has no key say so and do not change stock.
  • Shopify and Mirakl Connect inventory sync send a stock change to every product that carries the key.

Inventory feed imports

Carriyo can now import inventory snapshots from CSV files you drop in your Amazon S3 bucket or Azure Blob Storage container. Carriyo picks up each file from an inbox folder, imports it, moves it to an archive folder, and writes an import result for the file.

Files use lowercase snake_case headers: inventory_key, location_code (or location_id), event_timestamp, on_hand and unavailable. There's no Dashboard page for feeds yet. Carriyo sets them up for you on request.

Inventory API changes

Inventory endpoints are keyed by inventory_key:

  • Stock updates. Each event in POST /inventory-events/bulk/import needs an inventory_key. Events that send product_id, product_ref or merchant are rejected. You can send location_code in place of location_id. An unknown or ambiguous location code is rejected, as is a code that points to a different location than location_id. Each result row returns the inventory_key and location_code you sent.
  • Stock reads. New GET /inventory/inventory-key/{inventory-key} and GET /inventory/product-ref/{product-ref}?merchant= sit alongside GET /inventory/product-id/{product-id}. The response returns inventory_key instead of product_id. Each entry in stock_by_location now includes available and no longer lists reservations.
  • Reservations. Reservations have their own endpoints: GET /inventory/inventory-key/{inventory-key}/reservations, plus product-id and product-ref versions. Each reservation shows the order, fulfillment order, product, quantity and location.
  • Event history. New GET /inventory-events/inventory-key/{inventory-key}. Both this and GET /inventory-events/product-id/{product-id} now list events newest first. They are paged with page_num and rows_per_page over the latest 500 events. The response is an object with items, total, page_num and rows_per_page, not a plain list. Filter by location_id.
  • Deleting a position. DELETE /inventory/inventory-key/{inventory-key}/location-id/{location-id} replaces the product-id path.
  • Inventory webhooks. Each webhook now covers one location. old_image and new_image hold that inventory position, with inventory_key, location_id, on_hand, reserved and unavailable. A webhook fires only when one of those numbers changes.

Inventory event ordering and statuses

Carriyo now checks each inventory event's event_timestamp against the inventory position before applying it.

  • A stock movement older than the position's latest snapshot is rejected, because the snapshot already counts it. Movements include sales, receipts, returns, transfers and adjustments.
  • A snapshot that matches the stored numbers is recorded as UNCHANGED. It changes nothing and sends no webhook.
  • Event status values are now APPLIED, REJECTED and UNCHANGED. REJECTED replaces NOT_APPLIED.
  • Timestamps up to 2 minutes in the future are accepted, to allow for clock differences.
  • Within one request, an event is dropped as a duplicate only when an earlier one has the same inventory key, location, event type and reason.

In the Dashboard, the product's History tab shows rejected events with the reason.

Inventory Positions

The Inventory Positions page under Settings > Administration > Inventory Positions lists stock by inventory key.

  • A summary row shows the number of SKUs, units on hand, and available, reserved, not available and out-of-stock quantities.
  • Search matches the inventory key, product reference, SKU, barcode or product name.
  • A new Inventory Key column can be sorted and filtered with is or is not.
  • Product links on orders and inventory rows open the product page under Products. Its Orders and History tabs show the reservations and event history for the product's inventory key.
  • The Inventory Location setting is locked on locations created by the Shopify app.

Also on the Products list: Export selected items exports only the products you select, and the category filter offers every category of the merchant.

Product delivery options: errors per product

POST /storefront/product-delivery-options no longer fails the whole request when one product is not in the catalog. That product keeps its row, with an empty delivery_options list and an error object whose code is PRODUCT_NOT_FOUND. The other products get their options as normal. Check error before reading delivery_options.

The row returns product_ref as sent, and product_id only when you sent it. An unknown product_id now gets the same error row. Problems with the request itself still return 400.

Carrier updates

  • Emirates Courier Express: marking a shipment ready to ship now books a pickup with the carrier and saves the pickup ID. The pickup window follows the shipment's scheduled collection. Without one, it opens an hour later and lasts eight hours. When the carrier refuses the pickup, the shipment shows a scheduling warning with the reason. The Special handling types setting adds RTS – Return to shipper.
  • Logistiq: carrier status 109, "Package collected from origin", now maps to In transit. Status 151, "Customer service hold after failed delivery attempt", maps to Suspended with reason Awaiting customer confirmation.

MCP inventory tools

The inventory tools now take inventory_key. get_inventory also finds stock by product_id, or by product_ref with merchant. get_inventory_history also takes product_id, pages its results and can filter by location. Product tools read and write inventory_key, and list_products and list_inventory filter by it.

Fixes

  • Logistiq tracking: status updates whose timestamps had no fractional seconds were dropped. They are now recorded.
  • UPS consolidated shipments: booking the consolidated parent shipment no longer fails for a missing description.
  • Consolidated shipment edits: saving a consolidated shipment in the Dashboard no longer asks for items. The form checks only packages or parcels, whichever the shipment uses.
  • Delivery option locations: searching for a location in the delivery option editor no longer fails with an access error.
  • Order cancellation: cancelling an order that is already cancelled now returns a 400 error instead of processing the cancellation again.
  • Return shipments: when some returned items cannot be matched to a parcel of the original shipment, the return shipment adds a default parcel for them.