Menu

Paging through stock changes

GET /api/v1/stock/changes lists the changes to your stock: adjustments, and reservations made and released. This page explains how to page through it, and how to poll it so you never miss a change.

What a page looks like

Changes come newest first, 50 per page unless you ask for another per_page (at most 200). The pagination object says where you are:

curl "https://picked.nl/api/v1/stock/changes?per_page=100&page=1" \
  -H "Authorization: Bearer $PICKED_TOKEN" \
  -H "Accept: application/json"
{
    "data": [
        {
            "id": 90211,
            "occurredAt": "2026-10-10T06:42:17+00:00",
            "eventType": "stock_adjusted",
            "productSku": "BEL-ZW-01",
            "warehouseCode": "UTR",
            "locationName": "A-03-2",
            "quantity": -2,
            "reason": "damaged",
            "movementUuid": "9b2f6c1e-4d7a-4f0b-8c3e-2a5d7e9f1b40"
        }
    ],
    "pagination": {
        "currentPage": 1,
        "perPage": 100,
        "lastPage": 1,
        "total": 1,
        "from": 1,
        "to": 1
    },
    "_links": {
        "self": { "href": "/api/v1/stock/changes?per_page=100&page=1" }
    }
}

Follow _links.next until it is absent, or ask for the next page until currentPage reaches lastPage. The keys of this endpoint are camelCase, unlike the rest of the API; v1 keeps them that way.

eventType is one of stock_adjusted, stock_reserved and stock_reservation_released. For stock_adjusted, quantity is signed: a negative number is stock that went out. For the two reservation types it is always positive.

Filters

Parameter Effect
since Changes at or after this moment, as an ISO 8601 date-time. A value without a time zone is read as UTC.
until Changes at or before this moment. A bare date, such as 2026-10-12, covers that whole day in UTC.
product_sku One product. An unknown SKU answers 404.
warehouse_id One warehouse.
location_id One location.
event_type One of the three event types above.
reason One reason, such as damaged.

A filter that matches nothing answers an empty page, not an error.

Polling without missing a change

Page numbers are for browsing. Because the newest change comes first, a change that lands while you are paging pushes everything down by one, and you would read one change twice or skip one. To keep a system in step, read with a cursor instead:

  1. The first time, ask for the changes after cursor 0: GET /api/v1/stock/changes/feed?after=0. They come oldest first, at most per_page at a time.
  2. Process them, then store the answer's nextCursor.
  3. While hasMore is true, follow _links.next straight away: it asks again with after set to that cursor and your filters kept. Once it is false, you have everything so far; wait for your next poll and ask again from the cursor you stored.
curl -G "https://picked.nl/api/v1/stock/changes/feed" \
  --data-urlencode "after=0" \
  --data-urlencode "per_page=200" \
  -H "Authorization: Bearer $PICKED_TOKEN" \
  -H "Accept: application/json"
{
    "data": [
        {
            "id": 90211,
            "occurredAt": "2026-10-10T06:42:17+00:00",
            "eventType": "stock_adjusted",
            "productSku": "BEL-ZW-01",
            "warehouseCode": "UTR",
            "locationName": "A-03-2",
            "quantity": -2,
            "reason": "damaged",
            "movementUuid": "9b2f6c1e-4d7a-4f0b-8c3e-2a5d7e9f1b40"
        }
    ],
    "nextCursor": "184467",
    "hasMore": false,
    "_links": {
        "self": { "href": "/api/v1/stock/changes/feed?after=0&per_page=200" }
    }
}

A change committed while you read lands after your cursor, so you neither skip it nor read it twice. With nothing new, nextCursor is the cursor you sent. Treat the cursor as an opaque string: store it and send it back, but don't compare or calculate with it. It stays valid for good. A change reaches the cursor about five seconds after it was saved; the wait lets changes saved a moment apart arrive in order. The feed takes the same filters as the pages.

Polling every minute or two stays well inside the stock endpoints' rate limit.

Changes or levels?

Stock changes tell you what happened to the stock on the shelves. They don't include what orders claim: when an order is accepted, it lowers the product's sellable figure without adding a stock change. So don't use the changes to decide which products to update in a webshop. Ask GET /api/v1/stock?changed_since=<moment> instead: it lists the levels of every product whose stock, order commitments or locations changed since then, sellable included. Keep your webshop's stock in sync shows how.