Product stock
Use the stock API when a merchant integration needs to read current product stock, evaluate past stock movements, apply stock corrections, or move stock between bin locations.
Available operations
- Read stock rows with stocks.
- Read warehouse totals with warehouseStocks, or load one by global ID with warehouseStock.
- Read product-level stock fields with products.
- Read booked stock movements with stockMovements, or load one by global ID with stockMovement.
- Set absolute stock at a bin location with setStockAtBinLocation.
- Set absolute stock for a warehouse with setStockAtWarehouse.
- Apply relative stock changes with adjustStockAtBinLocation or adjustStockAtWarehouse.
- Move stock between bin locations with transferStock.
- Create batch records with createBatch before writing batch-specific stock.
Stock concepts
A product carries four stock counters. Each one pairs with an inventory state in Shopify:
| Field | Meaning | Shopify counterpart |
|---|---|---|
physicalStock | Physical stock: the stock present in all warehouses. It always equals available plus reserved plus unavailable stock. | On hand |
availableStock | Available stock: what customers can buy, that is physicalStock minus reservedStock minus stockNotAvailableForSale. It becomes negative when more is reserved than is present. | Available |
reservedStock | Reserved stock: stock assigned to fulfillment orders that are not yet shipped. | Committed |
stockNotAvailableForSale | Unavailable stock: stock that is neither for sale nor reserved, such as stock in a warehouse whose isStockAvailableForSale is false. | Unavailable |
Below the product level, stock is kept in two lists:
warehouseStockshas one row per product and warehouse, summed over all bin locations of the warehouse.stockshas one row per product and stock location.locationTypeTechnicalNamesays which kind of location a row belongs to:bin_locationfor a bin location,warehousefor the unknown bin location of a warehouse (stock in a warehouse whose exact spot is not known), andstock_container,goods_receipt,order,return_orderorspecial_stock_locationfor stock outside the shelves.
Example: read the stock of a product per bin location
Without a filter on the location type, stocks also returns the rows outside the warehouse shelves: stock booked onto orders by shipping, stock on goods receipts or return orders, and the special stock locations that act as the counterpart of corrections and imports. A special stock location usually holds a negative quantity, because a correction that adds five units to a bin location takes them from stock_correction. Filter on locationTypeTechnicalName to read only the stock on the shelves:
query StockByProduct($productId: ID!, $cursor: String) {
stocks(
first: 100
after: $cursor
filter: {
productId: { equals: $productId }
locationTypeTechnicalName: { in: ["bin_location", "warehouse"] }
}
) {
edges {
node {
locationTypeTechnicalName
quantity
binLocation {
code
warehouse {
code
}
}
warehouse {
code
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
A bin_location row has no warehouse of its own: read the warehouse through binLocation.warehouse. A warehouse row has a warehouse and no binLocation. The default bin location of a product keeps a row with quantity 0 when it is empty.
stocks and warehouseStocks return their rows in the order of their IDs, which is stable for paging but carries no meaning, and the lists take no sort argument. A row holds the stock of all batches at its location; per-location batch stock is not readable through the API. Batch.physicalStock gives the total stock of a batch across all warehouses.
Example: read the stock movements of a day
Every stock change is booked as a stock movement from a source location to a destination location. Filter stockMovements by createdAt to read all movements of a period:
query StockMovementsOfDay($from: DateTime!, $to: DateTime!, $cursor: String) {
stockMovements(first: 200, after: $cursor, filter: { createdAt: { after: $from, before: $to } }) {
edges {
node {
id
productId
quantity
createdAt
processTypeTechnicalName
sourceLocationTypeTechnicalName
sourceBinLocationId
destinationLocationTypeTechnicalName
destinationBinLocationId
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Both bounds are exclusive: after and before match only movements strictly after or before the given instant. To read all of 6 October 2026, start one millisecond before midnight, and write both values in UTC with the offset +00:00:
{
"from": "2026-10-05T23:59:59.999+00:00",
"to": "2026-10-07T00:00:00+00:00"
}
Stock movements are returned in the order they were booked. Follow endCursor until hasNextPage is false to read all of them; the API does not return a total count.
The page size is limited by the complexity of the request. A list counts its page size times one more than the number of fields selected inside it, and a single request may count at most 5,000. With the nine fields and pageInfo above, a page of 200 movements counts 3,000; a page of 500 would exceed the limit. Select fewer movements per page when you select more fields. The same number is charged against the rate limit.
quantity is always positive. Whether a movement adds or removes stock at a location follows from its source and destination. processTypeTechnicalName names the reason for the movement, for example supplier_order, shipping or relocation. The list of values is open because other Pickware products add their own, so map the values you need to your own categories and handle unknown ones. It is null for movements booked without a process, which includes all movements booked before March 2025.
Example: set the stock at a bin location
setStockAtBinLocation sets the stock of a product at a bin location to an absolute quantity, for example after counting the shelf:
mutation SetStockAtBinLocation($input: SetStockAtBinLocationInput!) {
setStockAtBinLocation(input: $input) {
id
quantity
binLocation {
code
}
}
}
{
"input": {
"productId": "UHJvZHVjdDow...",
"binLocationId": "QmluTG9jYXRpb246MDE4...",
"quantity": 12
}
}
The mutation computes the difference to the current stock and books it as one stock movement from or to the special stock location stock_correction, with the process type api_incoming or api_outgoing. When the stock already equals quantity, nothing is booked.
Example: adjust stock and transfer it between bin locations
adjustStockAtBinLocation books a relative change instead, which suits integrations that only know what happened, not what is on the shelf:
mutation AdjustStockAtBinLocation($input: AdjustStockAtBinLocationInput!) {
adjustStockAtBinLocation(input: $input) {
id
quantity
}
}
{
"input": {
"productId": "UHJvZHVjdDow...",
"binLocationId": "QmluTG9jYXRpb246MDE4...",
"delta": -3
}
}
transferStock moves stock from one bin location to another in a single movement with the process type api_relocation, and returns the stock at the destination:
mutation TransferStock($input: TransferStockInput!) {
transferStock(input: $input) {
id
quantity
binLocation {
code
}
}
}
{
"input": {
"productId": "UHJvZHVjdDow...",
"sourceBinLocationId": "QmluTG9jYXRpb246MDE4...",
"destinationBinLocationId": "QmluTG9jYXRpb246MDE5...",
"quantity": 5
}
}
These rules apply to the stock mutations:
- No mutation may take the stock at its source below zero. Removing more than a bin location holds, with a negative
deltaor atransferStockquantity, fails and books nothing. deltamust not be0.transferStockneeds a positivequantityand two different bin locations.quantityofsetStockAtBinLocationandsetStockAtWarehousemust be zero or more.setStockAtWarehouseandadjustStockAtWarehousebook the difference on the unknown bin location of the warehouse, while the warehouse total also counts its bin locations. Lowering a warehouse's stock therefore fails as soon as the unknown bin location does not hold enough; correct the bin locations instead.- A
setStockAtBinLocationoradjustStockAtBinLocationthat empties a bin location returnsnull, because an empty bin location has no stock row, unless it is the product's default bin location. - For a product whose stock is not managed by Pickware ERP, stock cannot be booked onto a bin location.
Batches
Pass batchId on a stock mutation to write the stock of a single batch of a product with batch management. Create the batch with createBatch first.