Menu

Follow order changes

Keep your system in step with every order you sent to Picked: each time an order takes a status, and each time one of its shipments is created, gets its label, is handed to the carrier, or is delivered, fails or comes back. One call reads everything that changed since your last poll.

You need a token with the orders:read ability (see Authentication).

Read the changes after a cursor

  1. The first time, ask for the changes after cursor 0: GET /api/v1/orders/changes?after=0. They come oldest first, at most per_page at a time (50 by default, 200 at most).
  2. Process them, then store the answer's next_cursor.
  3. While _links.next is there, follow it straight away. Once it is absent, you have everything so far; wait for your next poll and ask again with after set to the cursor you stored.
curl -G "https://picked.nl/api/v1/orders/changes" \
  --data-urlencode "after=0" \
  --data-urlencode "per_page=200" \
  -H "Authorization: Bearer $PICKED_TOKEN" \
  -H "Accept: application/json"
{
    "data": [
        {
            "cursor": "771042",
            "type": "shipment.dispatched",
            "occurred_at": "2026-10-12T15:48:09+02:00",
            "status": null,
            "order": {
                "id": 48211,
                "external_id": "WS-2026-104233",
                "status": "packed",
                "shipped_short": false,
                "fulfilment_method": "carrier",
                "warehouse_id": 12,
                "shipments": [
                    {
                        "id": 30554,
                        "carrier": "postnl",
                        "tracking_number": "3SPCKD0104233",
                        "tracking_url": "https://postnl.nl/tracktrace/?B=3SPCKD0104233&T=C",
                        "status": "handed_over",
                        "dispatched_at": "2026-10-12T15:48:09+02:00",
                        "parcels": []
                    }
                ],
                "updated_at": "2026-10-12T15:48:09+02:00",
                "_links": { "self": { "href": "/api/v1/orders/48211" } }
            },
            "_links": { "order": { "href": "/api/v1/orders/48211" } }
        }
    ],
    "next_cursor": "771042",
    "has_more": true,
    "_links": {
        "self": { "href": "/api/v1/orders/changes?after=771000&per_page=1" },
        "next": { "href": "/api/v1/orders/changes?after=771042&per_page=1" }
    }
}

What a change says

type When
order.status_changed The order took a status; status says which.
shipment.created The warehouse created a shipment for the order.
shipment.carrier_changed It moved to another carrier before its label was bought.
shipment.label_created Its label was bought: tracking_number is known from here on.
shipment.dispatched It was handed to the carrier.
shipment.delivered The carrier delivered it.
shipment.failed The carrier couldn't deliver it.
shipment.returned It came back to the warehouse.

Each change embeds the order as it is now, with its shipments and tracking, and _links.order reads the whole order with its lines. So an order that moved on since shows its newest status on its older changes too: read status on an order.status_changed for the status that change was about.

Cursors and timing

Every change comes once, in the order it happened. With nothing new, next_cursor 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 feed about five seconds after it was saved; the wait lets changes saved a moment apart arrive in order. A change to an order that was deleted since is passed over.

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