Guide

The inventory model

Why quantity, offered stock and incoming differ, how warehouses feed the offer, and which number to send to a channel or an ERP.

·8 min read ·Updated 3 Sep 2026

An inventory record carries several numbers that all look like “how many do we have”. They are not interchangeable, and the two surfaces answer different questions.

Two surfaces, two shapes

GET /inventory returns the stored record for an item:

{
  "id": 34897,
  "sku": "201156",
  "item_name": "Vichy Homme Structure Force 50 ml",
  "quantity": 150,
  "reserved_quantity": 3,
  "incoming_quantity": 50,
  "backorder_amount": 0,
  "bin_location": ["A1-001-01", "B2-003-05"],
  "threshold": "5u"
}

The inventory.stock_changed webhook sends a different, purpose built payload, because stored fields alone cannot express multi warehouse stock unambiguously:

{
  "id": 34897,
  "sku": "201156",
  "offered_stock": 3,
  "total_on_hand": 4,
  "incoming": 12,
  "backorder": 0,
  "quantities": [
    {"warehouse": "Main", "warehouse_id": "WH1", "is_default": true,
     "sums_onto_offered": true, "available": 3, "reserved": 1, "on_hand": 4, "inbound": 12},
    {"warehouse": "Amazon FBA", "warehouse_id": "WH2", "is_default": false,
     "sums_onto_offered": false, "available": 1, "reserved": 0, "on_hand": 1, "inbound": 6}
  ]
}

offered_stock is not on the REST record. It only arrives on the webhook payload. If you need it and you are polling rather than subscribing, that is the reason to subscribe. See the webhook delivery contract.

What each number means

FieldWhereQuestion it answers
quantityRESTUnits on the record, including units allocated to open orders
reserved_quantityRESTHow many of those are already spoken for
incoming_quantityRESTUnits on open purchase orders
offered_stockWebhookWhat Stockpilot advertises to your sales channels
total_on_handWebhookOn hand across contributing warehouses, including reserved
quantities[].availableWebhookOn hand in that warehouse, excluding reserved
quantities[].on_handWebhookOn hand in that warehouse, including reserved

offered_stock is derived, not stored: the sum of warehouses flagged to feed the offer, minus buffer stock.

Why offered_stock differs from what is on the shelf

Two mechanisms pull it away from the physical count.

Warehouses that do not feed the offer

Each warehouse carries a sums_onto_offered flag. In the payload above, the Amazon FBA warehouse holds a unit that Amazon sells and ships directly. Offering it again on Shopify would oversell, so it counts towards total_on_hand and not towards offered_stock.

Reserved and buffer stock

Units allocated to open orders are reserved and are not available to sell again. A seller can also hold back a buffer so a channel never sees the last few units. Both come off before the offer is published, which is why offered_stock (3) sits below total_on_hand (4) in the example.

Which number to use

You are buildingUse
A channel listing syncoffered_stock
An ERP availability feedoffered_stock
A physical stock take reconciliationquantities[].on_hand per warehouse
A replenishment calculationtotal_on_hand plus incoming
A warehouse picking viewquantities[].available for that warehouse

Summing quantities[].on_hand yourself gives you the physical count, not offered_stock. It ignores the sums_onto_offered flag, reserved units and buffer stock, and will overstate availability for any seller with more than one warehouse.

The inbound double count

Per warehouse records also carry an inbound figure:

{"warehouse": "Main", "warehouse_id": "WH1", "is_default": true,
 "sums_onto_offered": true, "available": 3, "reserved": 1, "on_hand": 4, "inbound": 12}

Do not sum inbound across warehouses. On the default warehouse, per warehouse inbound repeats the product level incoming, because purchase orders land there. Adding the per warehouse values together and comparing to incoming double counts every inbound unit.

Use the product level incoming field.

Locations and thresholds

Two more fields that ride along on an inventory record:

  • bin_location is an array of hierarchical bins, written A1-001-01. One item can sit in several. Do not parse a bin into aisle, rack and shelf unless you know how that specific warehouse is laid out.
  • threshold is either units (5u) or weeks of cover (33w). The weeks form is demand aware: a fast selling SKU trips its threshold long before a slow one holding the same unit count.

Reading it

GET /inventory for a page of items, GET /inventory/get for one by id, sku or barcode, and GET /warehouses/{unique_id}/items when you need one warehouse’s view specifically.

curl -s "https://api.stockpilot.dev/inventory?page_size=1000" \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"

Keeping it fresh

Polling the list works and is the right tool for a nightly reconciliation. For anything closer to real time, subscribe to inventory.stock_changed, which delivers the same fields within seconds of a change. Build it with Sync offered stock to an ERP.

A Stockpilot engineer talking through an integration with a developer

Stuck on something

Ask a person, not a search box

The API surface is wide, and some of it only makes sense once someone explains why it works that way. If a payload is not doing what you expect, or you are weighing two approaches, say so and we will look at it with you.