The inventory module tracks stock at the SKU level across one or more locations. Every sale, return, transfer, or count writes a transaction; current stock is the running sum. The cart layer reserves stock at add-to-cart time so a popular SKU doesn't double-sell during a flash sale.
Reading stock
/storefront/inventory/sku/:skuJWT/storefront/inventory/sku/:sku/location/:locationIdJWT/storefront/inventory/location/:locationIdJWT/storefront/inventory/alerts/low-stockJWT/storefront/inventory/statsJWT/storefront/inventory/sku/:sku/transactionsJWTThe first endpoint returns total available across every location, broken down per location. The second returns just one location. Low-stock alerts are SKU/location pairs at or below their reorderLevel. Stats roll up the catalogue: total units on hand, total value, units sold last N days.
// GET /storefront/inventory/sku/SNK-AIR-1-9-BLK
{
"sku": "SNK-AIR-1-9-BLK",
"available": 42,
"reserved": 5,
"onHand": 47,
"byLocation": [
{ "locationId": "loc-warehouse-1", "available": 30, "reserved": 3 },
{ "locationId": "loc-store-sf", "available": 12, "reserved": 2 }
],
"reorderLevel": 10,
"reorderQuantity": 50
}
onHand = available + reserved. available is what a new add-to-cart can grab.
Movements
/storefront/inventoryJWT/storefront/inventory/adjustJWT/storefront/inventory/reserveJWT/storefront/inventory/releaseJWT/storefront/inventory/fulfillJWT/storefront/inventory/returnJWT/storefront/inventory/countJWTThe base POST creates a stock record (initial seed). The others are typed transactions:
| Endpoint | Effect | Used by |
|---|---|---|
adjust | Manual +/- with a reason code | Cycle counts, write-offs, found stock |
reserve | Move available → reserved | Cart add, order pending |
release | Move reserved → available | Cart abandonment, order cancel |
fulfill | Decrement reserved (commits the sale) | Shipment created |
return | Increment available | RMA inspection passes |
count | Set absolute on-hand to a counted value | Physical inventory |
// reserve 2 units for an order
await fetch('/api/storefront/inventory/reserve', {
method: 'POST',
headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
body: JSON.stringify({
sku: 'SNK-AIR-1-9-BLK',
locationId: 'loc-warehouse-1',
quantity: 2,
referenceType: 'order',
referenceId: 'ord-12345',
}),
});
Reservations carry a reference so they can be released by reference rather than by ID — the order-cancellation flow does this without needing to remember the reservation ID.
Reservation and stock-rule semantics
The reservation model prevents oversells but is timed:
- Cart adds reserve immediately and the reservation expires when the cart is cleared or after the org-configured TTL (default 60 minutes).
- Order placement converts the reservation into a "pending fulfillment" that doesn't expire.
- Shipment via the logistics module calls
fulfill, decrementing reserved.
If you sell at very high volume, configure the cart-reservation TTL to match your typical checkout time and not much longer. A long TTL means a flash-sale cart hoards stock the customer never buys.
Transfers between locations
/storefront/inventory/transfersJWT/storefront/inventory/transfersJWT/storefront/inventory/transfers/:transferIdJWT/storefront/inventory/transfers/:transferId/shipJWT/storefront/inventory/transfers/:transferId/receiveJWT/storefront/inventory/transfers/:transferId/cancelJWTTransfers are a paired transaction: a ship debits the source location, a receive credits the destination, and the transit-stock value sits on the transfer record between the two events. Cancellation before ship reverses without affecting either location.
Low-stock automation
The low-stock alert endpoint feeds an Automation flow trigger called inventory.low-stock. Wire it to:
- email the buyer
- create a CRM task on the supplier contact
- post to Slack via the broadcast channel
- raise a purchase order in your ERP
Multi-location strategy
If your storefront is single-location, set one location ID and ignore the per-location endpoints. The aggregate endpoints work identically. If you have many locations, the order routing logic (in the storefront service) picks the closest fulfilling location to the customer's shipping address by default; you can override with a manual fulfillFromLocationId on the order.
Inventory transactions are the source of truth. The available cache is rebuilt on every read from the transaction sum — there's no drift between the displayed stock and the underlying ledger.