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
- The first time, ask for the changes after cursor
0:GET /api/v1/orders/changes?after=0. They come oldest first, at mostper_pageat a time (50 by default, 200 at most). - Process them, then store the answer's
next_cursor. - While
_links.nextis there, follow it straight away. Once it is absent, you have everything so far; wait for your next poll and ask again withafterset 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.