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.
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
| Field | Where | Question it answers |
|---|---|---|
quantity | REST | Units on the record, including units allocated to open orders |
reserved_quantity | REST | How many of those are already spoken for |
incoming_quantity | REST | Units on open purchase orders |
offered_stock | Webhook | What Stockpilot advertises to your sales channels |
total_on_hand | Webhook | On hand across contributing warehouses, including reserved |
quantities[].available | Webhook | On hand in that warehouse, excluding reserved |
quantities[].on_hand | Webhook | On 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 building | Use |
|---|---|
| A channel listing sync | offered_stock |
| An ERP availability feed | offered_stock |
| A physical stock take reconciliation | quantities[].on_hand per warehouse |
| A replenishment calculation | total_on_hand plus incoming |
| A warehouse picking view | quantities[].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_locationis an array of hierarchical bins, writtenA1-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.thresholdis 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.
