Inventory
Reading stock levels, adjusting them, and the rollup that will overwrite your writes if you ignore it.
GET /api/v1/inventory
GET /api/v1/inventory/{id}
PATCH /api/v1/inventory/{id}
Reading needs read_inventory. Writing needs write_inventory.
Reading Stock
The list gives current levels across the catalogue. A single read gives one product or variant.
curl -s https://shop.example.com/api/v1/inventory \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"
Full response, two products of fifty
{
"current_page": 1,
"data": [
{
"id": 11,
"name": "Annibale Colombo Bed",
"sku": "DUM-00011",
"stock_qty": 158,
"low_stock_threshold": 5,
"allow_backorders": false,
"track_inventory": true,
"status": "active"
},
{
"id": 12,
"name": "Annibale Colombo Sofa",
"sku": "DUM-00012",
"stock_qty": 100,
"low_stock_threshold": 5,
"allow_backorders": false,
"track_inventory": true,
"status": "active"
}
],
"first_page_url": "https://demo.unifycart.com/api/v1/inventory?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "https://demo.unifycart.com/api/v1/inventory?page=1",
"links": [
{
"url": null,
"label": "« Previous",
"page": null,
"active": false
},
{
"url": "https://demo.unifycart.com/api/v1/inventory?page=1",
"label": "1",
"page": 1,
"active": true
},
{
"url": null,
"label": "Next »",
"page": null,
"active": false
}
],
"next_page_url": null,
"path": "https://demo.unifycart.com/api/v1/inventory",
"per_page": 50,
"prev_page_url": null,
"to": 50,
"total": 50
}
Reading one product returns the product's own figures and a row for each of its variants, with the variant named by its option values.
curl -s https://shop.example.com/api/v1/inventory/38 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"
Full response for one product
{
"data": {
"product": {
"id": 38,
"name": "Rice",
"sku": "DUM-00038",
"stock_qty": 42,
"low_stock_threshold": 5,
"allow_backorders": false,
"track_inventory": true,
"status": "active"
},
"variants": []
}
}
Stock lives on the variant where a product has them, and on the product where it does not. An integration that only reads product-level stock will see a sum and miss which edition it belongs to.
Adjusting Stock
PATCH /api/v1/inventory/{id} sets a level. Every adjustment is written to the shop's inventory log with a reason, which is what the merchant sees in the product's history.
Send a reason that will mean something in six months. "Warehouse sync" is useful. "API" is not.
The Rollup, Which Will Catch You
A shop can hold stock in several locations. When it does, the product's overall figure is recalculated as the sum of its locations whenever any location row is written.
Writing a product total on a multi-location shop is temporary
Set a product's overall quantity on a shop that uses locations, and your figure survives only until the next location write recalculates it from the sum. The integration appears to work and then silently stops.
Decide which model the shop is using and write at that level consistently. If locations are in use, write locations.
This has caused real data loss. A stock import that mixed the two levels turned 248,000 units into 9,000 on a live shop.
Stock Is Not the Whole Answer
Whether something can be bought depends on more than the number.
A product with track_inventory off sells regardless. A product with backorders allowed sells past zero. A variant that is inactive does not sell at any quantity.
An integration deciding whether something is available must read those too, or it will report things as out of stock that are selling perfectly well.
Reserved Stock
Stock comes down when an order is placed, before payment. So the figure you read already excludes goods held by unpaid orders.
That is usually what you want. If you are reconciling against a physical count, those goods are still on your shelf, which explains a discrepancy that is otherwise baffling.
Adjusting One Product
curl -s -X PATCH https://shop.example.com/api/v1/inventory/42 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "stock_qty": 120, "low_stock_threshold": 10 }'
| Field | Notes |
|---|---|
stock_qty |
The new quantity, required |
low_stock_threshold |
The level at which the merchant is warned |
What comes back after a write
{
"message": "Inventory updated.",
"data": {
"id": 38,
"name": "Rice",
"sku": "DUM-00038",
"stock_qty": 40,
"low_stock_threshold": 5
}
}
The response confirms the stored figure rather than echoing what you sent. Read it back rather than assuming: on a shop with several stock locations the number you asked for is not necessarily the number that ends up on the product, for the reason above.
Filtering the List
| Parameter | Does |
|---|---|
low_stock |
Only items at or below their threshold |
out_of_stock |
Only items at zero |
per_page |
Page size, default 50 |
page |
Which page |
low_stock is the useful one for a reordering integration, and it respects each product's own threshold rather than a figure you choose.
A product with no threshold never appears as low stock
That is not a fault. The merchant has not said what low means for that product, so nothing can be concluded. If you depend on this filter, it is worth telling the merchant that products without a threshold are invisible to it.
A Safe Sync Pattern
Read the shop's levels. Compare against yours. Write only the differences, with a reason naming your system.
Writing every level every time works, costs far more requests, and makes the merchant's inventory history useless because every product shows a daily adjustment that changed nothing.
