Skip to content

Recipe: keep stock levels in sync

Read the current level, then push a correction with PUT for one product or POST /inventory/batch for many, using Idempotency-Key so a retried request never double-applies.

GET /products/{id}/inventory returns one row per live outlet the product is stocked at.

Terminal window
curl https://api.oletapp.com/your-store/v1/products/PRODUCT_ID/inventory \
-H "Authorization: Bearer olet_live_YOUR_API_KEY"
{
"data": [
{
"outlet_id": "...",
"on_hand": 42,
"reorder_method": "none",
"reorder_point": null,
"reorder_amount": null,
"reorder_target": null,
"version": "..."
}
]
}

PUT /products/{id}/inventory/{outlet_id} sets an absolute level. Send the same on_hand twice and the second call changes nothing (changed: false), so it is safe to retry without an Idempotency-Key, though you may still send one. Without a reason_id, the change is recorded under the system reason “Stock set via API”. GET /inventory/adjustment-reasons lists the reasons you can name instead.

Terminal window
curl -X PUT https://api.oletapp.com/your-store/v1/products/PRODUCT_ID/inventory/OUTLET_ID \
-H "Authorization: Bearer olet_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"on_hand": 37}'
{
"data": {
"changed": true,
"previous": 42,
"on_hand": 37,
"delta": -5,
"adjustment_id": "...",
"movement_id": "..."
}
}

POST /inventory/batch sets or adjusts up to 100 product/outlet lines in one all-or-nothing request. Each line names exactly one of set (absolute) or adjust (relative), and exactly one of product_id or sku. Idempotency-Key is required here: a retried batch replayed without one could double-apply a relative adjust line.

Terminal window
curl -X POST https://api.oletapp.com/your-store/v1/inventory/batch \
-H "Authorization: Bearer olet_live_YOUR_API_KEY" \
-H "Idempotency-Key: YOUR_UNIQUE_KEY" \
-H "Content-Type: application/json" \
-d '{"lines": [
{"sku": "SKU-001", "outlet_id": "OUTLET_ID", "set": {"on_hand": 37}},
{"sku": "SKU-002", "outlet_id": "OUTLET_ID", "set": {"on_hand": 12}}
]}'
{
"data": [
{ "product_id": "...", "sku": "SKU-001", "outlet_id": "OUTLET_ID", "changed": true, "previous": 42, "on_hand": 37, "delta": -5 },
{ "product_id": "...", "sku": "SKU-002", "outlet_id": "OUTLET_ID", "changed": true, "previous": 10, "on_hand": 12, "delta": 2 }
]
}

A batch with a bad line, such as an unknown sku, writes nothing at all: the whole request fails or the whole request succeeds. The same product and outlet pair can appear only once in a batch. A replayed batch marks each line "replayed": true.

How do I find out when stock changes on the OLET side

Section titled “How do I find out when stock changes on the OLET side”

There is no delta-sync feed for stock levels the way there is for products and sales. Poll GET /products/{id}/inventory for the products you care about on whatever interval fits your integration, and treat the returned on_hand as the current truth.

What if I only have a SKU, not a product id

Section titled “What if I only have a SKU, not a product id”

The batch endpoint and POST /inventory/adjustments accept sku as an alternative to product_id, matched case-insensitively against live products only, never against a barcode. PUT /products/{id}/inventory/{outlet_id} takes the product id in its path, so use GET /products?sku=... to find the id first.

  • 422 INSUFFICIENT_STOCK when an adjustment would take stock below zero.
  • 422 REASON_NOT_ALLOWED when the reason is a system reason or is turned off.
  • 422 REASON_DIRECTION_MISMATCH when a reason_id on a set does not match whether stock went up or down.
  • 422 OUTLET_DELETED when the outlet has been deleted.

See idempotency for the full replay rules, or errors for what 422 INSUFFICIENT_STOCK and 422 NOT_STOCK_TRACKED mean.