Skip to content
Get started

Keeping stock in sync

Read stock levels and follow stock changes over time without missing or repeating records.

There are two ways to get stock out of Colleqtive, and they answer different questions.

“What is the stock right now?” Use GET /storeproducts/stock. It returns the current level per product per store.

“What changed since I last looked?” Use GET /products/stock/storeproductlogs. It returns individual movements: losses, gains, corrections and counts. This is the one to use for an ongoing sync, because a total tells you nothing about why it moved.

Terminal window
curl "$BASE_URL/api/v2/public/storeproducts/stock?store_number=001&page_size=100" \
-H "Authorization: Bearer $COLLEQTIVE_BEARER_TOKEN"

Two flags change the shape of the response:

  • include_locations=true breaks each product down per location. This makes the response much larger, so only ask for it when you need location detail.
  • exclude_customer_order_stock=true leaves out stock already reserved for customer orders, which gives you what is genuinely available to sell.

For a single product, use GET /stores/{store_number}/products/{product_number}/stock. Lines with no stock are left out of that response unless you pass include_zero_lines=true. Pass it when you are reconciling against a full product list, otherwise products that dropped to zero look like they disappeared.

The movement log supports two ways of paging forward. Both work. Pick one and stay with it.

last_modified_date is a lower bound and it is exclusive. Only movements recorded strictly after that moment come back. That is deliberate: store the newest timestamp you have seen, send it back next time, and you will not read the same record twice.

Terminal window
curl "$BASE_URL/api/v2/public/products/stock/storeproductlogs?last_modified_date=2026-08-26T09:00:00&page_size=500" \
-H "Authorization: Bearer $COLLEQTIVE_BEARER_TOKEN"

to_modified_date is the matching upper bound, also exclusive. Use both together to read a fixed window, for example when backfilling one day at a time.

is_historic decides which set of records you read. There are two, held in separate tables, and a movement is in one or the other.

Left out, or false. You read the current movement log. The date filters apply to when the movement was recorded, and both bounds are exclusive.

Set to true. You read the archived movement history instead. The date filters now apply to when the stock moved, and both bounds are inclusive.

Two consequences worth planning for:

  • A sync that only ever calls the default never sees archived movements. If you need the full picture, you have to read both.
  • Because the bounds flip from exclusive to inclusive, passing back the newest timestamp you saw will return that record a second time when is_historic=true. Use from_id paging there, or drop the duplicate yourself.

Every movement has an identifier that never changes. from_id is an inclusive lower bound:

Terminal window
curl "$BASE_URL/api/v2/public/products/stock/storeproductlogs?from_id=184320&page_size=500" \
-H "Authorization: Bearer $COLLEQTIVE_BEARER_TOKEN"

Store the highest id you processed and send it as from_id next time. to_id closes the range, but it is only applied when from_id is also set, and it is ignored when it is lower than from_id.

is_loss and is_gain filter on the class of the reason code rather than on a single code. Leave them out for no filtering, true to keep only that class, false to exclude it. This saves you from fetching the reason code table and passing raw codes.

Terminal window
# only losses, for one store, since a known point
curl "$BASE_URL/api/v2/public/products/stock/storeproductlogs?store_number=001&is_loss=true&from_id=184320" \
-H "Authorization: Bearer $COLLEQTIVE_BEARER_TOKEN"

reason_code filters on one specific code when you do need that precision.

article_number matches exactly. A partial product number returns nothing rather than a partial match.