Push orders from your ERP or webshop
Send each new order to Picked as soon as your system accepts it. Picked holds its stock straight away and puts it into the warehouse's work.
When to use this
Use this recipe when your shop or ERP isn't one of the built-in connectors, or when your own system decides which orders go to the warehouse.
You need:
- a token with the
orders:writeability, andorders:readto follow the order (see Authentication); - the id of the warehouse that ships the order:
GET /api/v1/warehouseslists your warehouses with theirwarehouse_id(see Getting started).
Send the order
POST /api/v1/orders takes one order. external_id is your own order number. Each line names its product by product_external_ref, your system's id for the article, plus its SKU and barcode where you have them. product_external_ref is matched against the External reference of the products you manage in Picked. A product a connected ERP or shop imported carries that system's id as its reference, so it is matched by its SKU or barcode instead: send them whenever you have them.
curl -X POST "https://picked.nl/api/v1/orders" \
-H "Authorization: Bearer $PICKED_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"warehouse_id": 12,
"external_id": "WS-2026-104233",
"customer_name": "Sanne de Vries",
"shipping_method": "postnl-standaard",
"ordered_at": "2026-10-13T21:47:00+02:00",
"sla_due_at": "2026-10-14T17:00:00+02:00",
"lines": [
{ "product_external_ref": "art-10021", "product_sku": "BEL-ZW-01", "quantity_ordered": 2 },
{ "product_external_ref": "art-55102", "product_sku": "BIDON-750", "quantity_ordered": 1 }
]
}'
HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/v1/orders/48211
{
"id": 48211,
"external_id": "WS-2026-104233",
"status": "pending",
"shipped_short": false,
"warehouse_id": 12,
"line_count": 2,
"provisioned_products": [
{ "external_ref": "art-55102", "product_id": 9135, "sku": "BIDON-750" }
],
"_links": {
"order": { "href": "/api/v1/orders/48211" }
}
}
An order has 1 to 500 lines. The API reference lists every optional field: customer_ref, notes, priority, currency, and unit_price_cents per line, which a shipment across a customs border needs.
Retry safely
Picked recognises an order by its external_id. If a call times out, send exactly the same request again. If the first call did create the order, you get it back with 200 OK instead of a second order. Either way, store the id from the answer you get. Idempotency explains the details.
What happens next in the warehouse
- The order appears under Orders in Picked with the status
pending. - Picked holds the stock it has for the order at once, so those units drop out of the
sellablefigure your stock sync reads. An order for more than is in stock is still accepted. - A product Picked doesn't know yet doesn't stop the order. Picked creates a placeholder product for it and lists it under
provisioned_products. Your managers find it under the products waiting for review (/wms/products/provisional), so someone can complete it. - From there the order follows the warehouse floor:
pending→assigned→picking→picked→packing→packed→shipped. An order for pickup goes throughready_for_pickupbeforeshipped. An order can becancelledat any point beforeshipped, except while it isready_for_pickup. - Two steps go back:
- When the pick run working on an order stops before the end, the order returns from
pickingtopendingto be planned again. It keeps what was already picked (picked_quantity), and only the rest is picked on the next run. - When a packer corrects a packing mistake, a
packedorder returns topackingand is packed again.
- When the pick run working on an order stops before the end, the order returns from
Follow the order to shipped
Read the order back with GET /api/v1/orders/{id}, the id the import answered: its Location header and _links.order point there. It needs a token with the orders:read ability. Right after the import it is still pending, and nothing has been counted yet:
curl "https://picked.nl/api/v1/orders/48211" \
-H "Authorization: Bearer $PICKED_TOKEN" \
-H "Accept: application/json"
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 48211,
"external_id": "WS-2026-104233",
"status": "pending",
"shipped_short": false,
"fulfilment_method": "carrier",
"warehouse_id": 12,
"lines": [
{ "sku": "BEL-ZW-01", "quantity": 2, "picked_quantity": null, "shipped_quantity": null, "closed_short_quantity": 0 },
{ "sku": "BIDON-750", "quantity": 1, "picked_quantity": null, "shipped_quantity": null, "closed_short_quantity": 0 }
],
"shipments": [],
"created_at": "2026-10-12T09:14:03+02:00",
"updated_at": "2026-10-12T09:14:03+02:00",
"_links": {
"self": { "href": "/api/v1/orders/48211" }
}
}
A shipment appears in shipments once the warehouse creates it; its tracking_number and tracking_url stay null until its label is bought, and for a carrier Picked isn't connected to. Then tracking_url opens the carrier's tracking page, and a shipment that went out as several parcels lists each one, with its own tracking, under parcels. That URL is null for a carrier that publishes no tracking link, and for one that ships through an aggregator. PostNL and DPD ask for the postcode on their page, because the API never shares the recipient's address. Once the order has gone out, it reads like this:
"status": "shipped",
"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": []
}
]
Per line, shipped_quantity and closed_short_quantity settle a short shipment: what is still owed is quantity minus both. picked_quantity and shipped_quantity stay null while the warehouse counts nothing for the order: before picking starts, and in a flow that ships without counting. Null means not counted, never none. The customer's name and address are never part of the answer. To hear about every order's changes without reading each one, poll the order changes feed.
Or let Picked push it
Rather than polling, subscribe a URL to order.shipped in Settings → Developers → Webhooks: Picked POSTs the order, tracking number included, the moment it leaves the building. Keep the poll as a nightly safety net. See Receive webhooks.
Errors to handle
| Status | What to do |
|---|---|
422 validation_failed |
Fix the request: errors names each field, such as an unknown warehouse_id or a line without quantity_ordered. Sending it again won't help. |
403 missing_ability |
The token lacks orders:write, or orders:read to read the order back. |
404 not_found |
Reading back: no order of yours has that id. Use the id the import answered, not your external_id. |
503 or another 5xx |
Send the same request again after a pause. The external_id keeps it from making a second order. |
Errors lists every code, and Rate limits what to do on a 429.
Try it with a dry run
Add the header Picked-Dry-Run: true to the same request to check the order against your real products and warehouses without writing anything. The answer is 200 with would: created, or already_exists for an external_id Picked has seen before. Dry runs walks through an order sample.