Skip to content

Ask AI

Ask anything about Destesi — setup, products, APIs.

AI answers may be wrong — always verify against the docs.

Inventory concepts

Inventory owns what a product is and how much of it exists. Item identity, catalog membership, quantities, reservations, allocations, fulfillment, returns and adjustments all live here and nowhere else in the suite.

Commerce and Relay are consumers. They call Inventory’s API over HTTP with their own integration credential, and they never hold stock of their own. There is no fallback: if Inventory has no items, or a consumer is not set up, or Inventory cannot be reached, the consumer does not start counting stock locally. An empty Inventory means an empty shop.

What a consumer keeps is its own layer on top: Commerce keeps price, publication and parcel for an item; Relay keeps which item its runs consume. Neither can authorize a stock movement from those records.

An item is one stockable thing with a workspace-unique SKU. Several items can be variants of one product: they share a group (“Camisa Oxford”) and differ in attributes ({"talla":"M","color":"Azul"}). Stock is always kept per item, so every size and colour has its own quantity.

Every item has a base unit and a scale (0 to 6 decimal places). Both are fixed when the item is created, together with any pack conversions, and cannot change afterwards — a history recorded in grams cannot quietly become kilograms.

  • Standard units convert within their dimension only: pc and each (count), g and kg (mass), ml and l (volume), cm and m (length).
  • Pack conversions are item-local labels such as box: {numerator: "12", denominator: "1"} — one box is twelve base units. A pack label cannot redefine a standard unit.
  • Quantities are decimal strings, never JSON numbers: "2.5", not 2.5. A quantity with more precision than the item’s scale is refused rather than rounded.

An item has no price. The one price Inventory stores is the publication price of an item linked to a Shopify store (see Shopify).

An item may carry a listed_quantity: what sales channels are told exists. It is not on-hand stock. No movement, reservation or order reads or changes it, and orders consume only real stock. It exists so a store that advertises more than the shelf holds can keep advertising it while real stock is counted in.

Stock is kept in positions. A position is one combination of:

Dimension Values
Item Any item
Location A named place — a warehouse, a shelf, a van
Owner The workspace itself, or a named stock owner (a consignor, a customer whose goods you hold)
Condition usable, quarantine or damaged

Each position has three numbers, and one derived from them:

  • on hand — physically there.
  • held — promised to an order that is not confirmed yet.
  • allocated — promised to a confirmed order.
  • available — on hand minus held minus allocated.

Only usable stock is ever available. Quarantined and damaged quantities are reported as blocked. When orders promise more than is on hand, available stays at zero and the shortfall is reported as backordered (see Orders wait for stock).

On-hand stock changes only through a movement, and movements are append-only: an original is never edited or deleted. A mistake is corrected by a new movement that points at it.

Kind What it does
receive Stock arrives into a position
issue Stock leaves outside an order (consumption, a sample)
transfer Moves available stock to another location
owner Moves available stock to another owner
condition Moves available stock to another condition
resize Turns available stock of one variant into the next smaller size
adjust A signed correction with a required reason — a count
fulfill An order’s line leaves the shelf
reversal Undoes part or all of an earlier movement
return Goods issued or fulfilled come back

Every movement records who wrote it (recorded_by), whether Inventory’s assistant did (recorded_via: "assistant"), the quantity and unit you submitted, and the exact conversion applied. A fulfilled line also records how it was picked — by scan, with the code read, or by hand.

issue, transfer, owner, condition and resize move only available stock: units promised to an order cannot be moved out from under it. A downward adjust cannot consume committed stock either.

reverse_movement appends a linked inverse of an earlier movement. It is bounded by what is left of the original, so several partial reversals can never undo more than was recorded. A reversal cannot be reversed; reverse the original instead.

Reversing an issue or a fulfill is a physical return. It lands in quarantine by default, in the original location (even an archived one) unless you name another active location, and you choose usable only after inspecting the goods. A return does not reopen the order: its fulfillment history and reservation stay as they were.

A person cannot type stock into existence. A receive, or an upward adjust, from a person — through the web, the assistant, the CLI or MCP — must carry source_reference: "scan:<code>" naming a code that belongs to that item. Anything else is refused with 422 scan_required. The CSV opening-stock import is closed to people for the same reason.

Integrations are exempt because they carry their own evidence: Relay’s production output, for example.

Item labels printed by Inventory carry a per-sticker serial in their QR. A scanned receipt sends those serials, and a sticker that was already received is refused with 409 label_already_received — the same label cannot bring the same unit in twice. Reversing a receipt in full frees its stickers.

A code is a physical marking that resolves to exactly one item or one location in the workspace: a manufacturer’s GTIN, a supplier reference, an internal code Inventory minted, or a shelf label. A code can carry a pack_quantity — the code on a case of twelve counts twelve per scan. The quickstart covers labelling and the Scan screen; the reference lists the formats.

Attaching, minting and removing codes are administrator verbs (or a product role with items). Scanning is not.

An outbound order — stock promised to leave — is a reservation in the API. Each has one or more lines, each line on one position.

held ──confirm──▶ allocated ──fulfill──▶ partially_fulfilled ──▶ fulfilled
│ │ (or closed)
├─ release ─▶ released │
└─ deadline ─▶ expired dispatch stamps dispatched_at
Status Meaning
held Taken, not confirmed. Expires at its expires_at
allocated Confirmed; the warehouse can pick it
partially_fulfilled Some lines, or part of a line, have left
fulfilled Everything left the shelf
closed Some units left and the rest was released
released Cancelled before anything left
expired A hold that passed its deadline

Only a hold expires. A confirmed order never lapses however long it waits: a picker halfway through a box must not have the stock pulled away. Expiry needs no clock of the consumer’s; an overdue hold is expired inside the next read or command in that workspace.

Dispatch is a stamp, not a status. dispatch_reservation marks a fulfilled or closed order as handed to the carrier or customer. It moves no stock — the stock already left at fulfillment.

Creating a reservation never fails for lack of stock. The order is taken, and the shortfall shows up twice: the position reports it as backordered, and the order’s line reports how many units it is waiting for. Those units cannot be sold or issued to anyone else when they arrive.

Fulfilling is where physical stock is enforced: a line cannot be picked past what is on hand (409 insufficient_stock). The fix is to receive the goods when they arrive — never to receive or adjust stock that is not there.

When several open orders need the same last units, each line also reports claimed: how many of the units it needs are owed to an order committed earlier, and claimed_by names that order. Confirmed orders come before holds; confirmed ones are ranked by when their sale was committed. An order that cannot be prepared anyway does not hold back the ones behind it.

This is advice. The pick itself stays first come, first served, and nothing refuses a pick because of claimed.

A reservation created by an integration (a Commerce sale, say) belongs to that integration. Only it may confirm, release or dispatch it; a person — an administrator included — gets 409 integration_owned. The warehouse can still fulfill it once it is confirmed. If that integration is revoked, an administrator may release the hold, but never confirm it.

A confirmed order still unfinished 48 hours after confirmation raises an outbound_stale alert (below). Orders held by a live integration are skipped, because the owning app alerts on those itself.

Archiving a location is refused while any position there has held or allocated stock. An archived location refuses receipts, upward adjustments, transfers into it and new reservations, but still allows moving stock out and recording returns. Renaming and archiving locations, and creating locations and stock owners, are administrator verbs.

A garment can be taken in, never let out. record_movement with kind resize turns available stock of one variant into the next smaller size of the same product — same group, same other attributes, same location, owner and condition — and nothing else. A 12 becomes a 10, never an 8, and a 10 never becomes a 12. When the product has no such variant the change is refused.

Which sizes exist is decided by the shop category: Commerce’s when Commerce has one, otherwise Inventory’s own store_category_kind setting. At the shelf a size change is scanned one garment at a time, with the old tag as code and the new tag as to_code. It needs the transfer permission.

Inventory follows the suite’s three layers.

Workspace role. Owners and administrators may run every operation. Configuration — locations, owners, settings, alert chats, integrations and Shopify — is administrator-only and cannot be granted to anyone else.

Product roles. Inventory’s permission catalog is the same vocabulary its integration credentials use:

items · receive · issue · transfer · adjust · reserve · fulfill · dispatch · reverse · owner · condition

A member with no Inventory role reads everything and may receive, issue, transfer, change size, reserve, fulfill and dispatch. Catalog edits (items), adjust, owner, condition and reverse need an administrator. A member with one or more Inventory roles may read, plus exactly the permissions those roles hold — which can include the administrator-only stock verbs above, never configuration. A refusal is 403 permission_required naming the missing permission.

Data scope. Stock owners are how a connected system is limited to part of the stock. An integration credential carries its operations (scopes) and the owners it may touch (owner_ids; by default, only workspace-owned stock). It never sees positions or movements of other owners, and it sees only the reservations it created. People are not limited by owner.

Commerce and Relay each receive one integration per workspace, provisioned by the suite with a fixed set of scopes:

App Scopes
Commerce read, reserve, fulfill, dispatch, reverse, items, receive
Relay read, issue

These appear in the workspace’s integration list but cannot be revoked (409 integration_managed): revoking one would stop that product’s catalog. Credentials you create yourself can be revoked at any time.

Every command takes an operation_key. Sending the same key with the same input returns the original answer without doing the work twice; the same key with different input is refused with 409. Choose one key per logical change and reuse it on every retry.

A consumer keeps a copy in step with two reads. get_snapshot returns the items, locations, owners, positions and reservations it may see together with a change cursor; list_changes then returns every committed change after that cursor, in order. Process a page before saving its cursor.

Low stock is one number per item, low_stock_at on list_items, and every surface quotes it rather than working it out again. By default it is a fixed threshold of 5 available units. In batch mode it is a share (25% by default, never less than one unit) of the variant’s last production run — everything received on the last day it was received — so a size made five at a time and one made forty at a time do not share a line.

Alerts go to Telegram chats an administrator links in Settings → Telegram. Each chat hears the categories you choose:

Category Raised when
low_stock An item counted in each crosses its low line, or any item sells out
outbound A new outbound order is created
adjustments An adjustment or a reversal is recorded

Chats subscribed to outbound also receive the outbound_stale reminder. An alert fires on the crossing: stock that is already low stays quiet.

A Shopify store connected in Connect can play two roles.

As a source, an administrator links it and loads it: every tracked, shippable variant becomes an item (gift cards and untracked variants are skipped), with its product as the group, its options as attributes and its barcode as a GTIN code. Shopify’s quantity becomes the item’s listed quantity — no stock is received. Loading again updates the same items instead of creating copies.

As a destination, publishing is off until an administrator turns it on. Then Inventory owns each product’s title, variants, SKU, barcode and quantity in Shopify, while price, images, tags and collections stay Shopify’s. Only locations you pair with a Shopify location are published, and only their usable, workspace-owned stock. An item without a publication price stays a draft there. If Shopify’s quantity changed outside Destesi — a sale Inventory has not heard about — Inventory reports drift and does not overwrite it.

Every operation reaches you three ways, all over the same contract:

  • The assistant, in the web app’s dock or at /chat. A member with Inventory roles is offered only the operations those roles allow. It pauses for approval before stock movements, reversals, the opening and catalog imports, integration grants and revocations, and every Shopify verb that changes the link or writes to the store. A credential it creates is shown in the browser, never in the conversation.
  • MCP — inventory_capabilities, inventory_query and inventory_command. Creating credentials is deliberately not possible there.
  • dst inventory, for a terminal or a script.

MCP and the CLI read the live operation list from Inventory, so a new operation is available on both without an update.