Recipes / Sync orders to your ERP
OrdersDrain a date range of orders with line items and addresses into your ERP, resolving every id the API emits along the way.
The pattern behind our wholesale and picking integrations.
The list is paginated: every page returns a count/total/data envelope plus your start/limit echoed back. Keep advancing start until it reaches total. Date ranges are inclusive, Europe/Amsterdam.
# drain all orders purchased on July 14th
START=0; TOTAL=1
while [ "$START" -lt "$TOTAL" ]; do
PAGE=$(curl -s "https://api.storekeeper.me/api/orders?from=2026-07-14&to=2026-07-14&start=$START&limit=100" \
-H "Authorization: Bearer $TOKEN")
TOTAL=$(echo "$PAGE" | jq .total)
START=$((START + $(echo "$PAGE" | jq .count)))
echo "$PAGE" | jq -c '.data[]' >> orders.jsonl
done
{
"from": "2026-07-14",
"to": "2026-07-14",
"start": 0,
"limit": 100,
"count": 100,
"total": 143,
"data": [
{
"id": 88231,
"order_number": "WEB-2026-04412",
"date_purchased": "2026-07-14T09:41:22+02:00",
"pickup_date": null,
"status": "complete",
"sub_status": null,
"shop_id": 12,
"location_id": 14,
"currency": "EUR",
"value_wt": 64.35,
"value_ex_wt": 59.04,
"discount_value_wt": 0,
"discount_value_ex_wt": 0,
"is_internal": false,
"is_paid": true,
"is_shipped": true,
"is_delivered": false,
"customer_reference": null,
"relation_data_id": 88440,
"customer_name": "Sanne de Vries",
"customer_email": "sanne@voorbeeld.nl",
"backoffice_url": "https://bakkerijjanssen.storekeepercloud.com/#order/details/88231"
}
]
}
is_internal=0 to exclude internal stock movements, and consider status (comma-separated, e.g. complete,shipped) so drafts and cancellations never reach your ledger.The detail call returns the same header fields plus lightly-shaped billing and shipping addresses. A missing id is a clean 404, so a deleted order cannot silently import as an empty record.
curl -s https://api.storekeeper.me/api/orders/88231 -H "Authorization: Bearer $TOKEN"
{
"id": 88231,
"order_number": "WEB-2026-04412",
"status": "complete",
"relation_data_id": 88440,
"value_wt": 64.35,
"value_ex_wt": 59.04,
"billing_address": {
"name": "Sanne de Vries",
"email": "sanne@voorbeeld.nl",
"phone": "+31612345678",
"address": { "street": "Kerkstraat", "streetnumber": "12", "zipcode": "1017 GL", "city": "Amsterdam", "country_iso2": "NL" }
},
"shipping_address": {
"name": "Sanne de Vries",
"email": "sanne@voorbeeld.nl",
"phone": "+31612345678",
"address": { "street": "Kerkstraat", "streetnumber": "12", "zipcode": "1017 GL", "city": "Amsterdam", "country_iso2": "NL" }
}
}
relation_data_id, and it equals the id in /api/customers. Use it as the foreign key to your ERP's debtor records; customer_name and customer_email are display snapshots, not identity.Every line carries a kind: product, shipping, payment, or discount. An ERP usually books only kind=product plus maybe shipping; payment and discount lines are bookkeeping artifacts you handle separately. Money fields with _wt are incl VAT, the plain ones are excl.
curl -s https://api.storekeeper.me/api/orders/88231/items -H "Authorization: Bearer $TOKEN"
{
"order_id": 88231,
"count": 3,
"truncated": false,
"data": [
{ "id": 301122, "order_id": 88231, "kind": "product", "sku": "BROOD-VLB-800",
"name": "Volkorenbrood 800g", "quantity": 2, "ppu": 3.12, "ppu_wt": 3.40,
"price": 6.24, "price_wt": 6.80, "tax_rate_id": 52,
"product_id": 4410, "shop_product_id": 9021, "pickup_date": null },
{ "id": 301123, "order_id": 88231, "kind": "product", "sku": "TAART-APPEL",
"name": "Appeltaart groot", "quantity": 1, "ppu": 48.21, "ppu_wt": 52.55,
"price": 48.21, "price_wt": 52.55, "tax_rate_id": 52,
"product_id": 4477, "shop_product_id": 9088, "pickup_date": "2026-07-15" },
{ "id": 301124, "order_id": 88231, "kind": "shipping", "sku": null,
"name": "Bezorging Amsterdam", "quantity": 1, "ppu": 4.13, "ppu_wt": 5.00,
"price": 4.13, "price_wt": 5.00, "tax_rate_id": 55,
"product_id": null, "shop_product_id": null, "pickup_date": null }
]
}
Orders carry a numeric shop_id (the sales channel: webshop, POS, marketplace). One cached call to /api/shops translates it to a name and, via location_id, to the physical branch.
curl -s https://api.storekeeper.me/api/shops -H "Authorization: Bearer $TOKEN"
{
"count": 2,
"total": 2,
"data": [
{ "id": 12, "name": "Webshop", "alias": "webshop", "location_id": 14, "relation_data_id": null },
{ "id": 15, "name": "Kassa Centrum", "alias": "pos-centrum", "location_id": 14, "relation_data_id": 20110 }
]
}
Key every imported record on the Storekeeper order id (stable, numeric) and store order_number for humans. On each sync, upsert by that key: insert when new, update when the status or payment flags changed. Re-running a whole day must then be a no-op, which also makes crash recovery trivial: just run the day again.
# per order, in your ERP importer
key = storekeeper.order.id # unique, never reused
display = storekeeper.order.order_number
UPSERT erp_orders SET status, is_paid, is_shipped, totals WHERE external_id = key
is_paid, is_shipped, and status flip days later. Sync a trailing window (yesterday and today, for example) rather than only "new since last run", and let the upsert absorb the overlap.Recepten / Orders synchroniseren naar je ERP
OrdersHaal een periode aan orders met orderregels en adressen naar je ERP, en vertaal onderweg elk id dat de API teruggeeft.
Het patroon achter onze groothandels- en pickintegraties.
De lijst is gepagineerd: elke pagina geeft een count/total/data-envelop plus je eigen start/limit terug. Blijf start ophogen tot hij total bereikt. Datumbereiken zijn inclusief, Europe/Amsterdam.
# alle orders gekocht op 14 juli leeglopen
START=0; TOTAL=1
while [ "$START" -lt "$TOTAL" ]; do
PAGE=$(curl -s "https://api.storekeeper.me/api/orders?from=2026-07-14&to=2026-07-14&start=$START&limit=100" \
-H "Authorization: Bearer $TOKEN")
TOTAL=$(echo "$PAGE" | jq .total)
START=$((START + $(echo "$PAGE" | jq .count)))
echo "$PAGE" | jq -c '.data[]' >> orders.jsonl
done
is_internal=0 mee om interne voorraadbewegingen uit te sluiten, en overweeg status (kommagescheiden, bijv. complete,shipped) zodat concepten en annuleringen nooit je grootboek bereiken.De detailaanroep geeft dezelfde headervelden plus licht gevormde factuur- en verzendadressen. Een onbekend id is een nette 404, dus een verwijderde order kan nooit stil als leeg record importeren.
curl -s https://api.storekeeper.me/api/orders/88231 -H "Authorization: Bearer $TOKEN"
relation_data_id, en die is gelijk aan het id in /api/customers. Gebruik hem als vreemde sleutel naar je debiteuren; customer_name en customer_email zijn weergave-snapshots, geen identiteit.Elke regel heeft een kind: product, shipping, payment of discount. Een ERP boekt meestal alleen kind=product plus eventueel shipping; betaal- en kortingsregels zijn boekhoudartefacten die je apart afhandelt. Geldvelden met _wt zijn incl. btw, de kale varianten excl.
curl -s https://api.storekeeper.me/api/orders/88231/items -H "Authorization: Bearer $TOKEN"
Orders dragen een numeriek shop_id (het verkoopkanaal: webshop, kassa, marktplaats). Eén gecachte aanroep naar /api/shops vertaalt dat naar een naam en, via location_id, naar de fysieke vestiging.
curl -s https://api.storekeeper.me/api/shops -H "Authorization: Bearer $TOKEN"
Sleutel elk geïmporteerd record op het Storekeeper-order-id (stabiel, numeriek) en bewaar order_number voor mensen. Upsert bij elke sync op die sleutel: invoegen als nieuw, bijwerken als de status of betaalvlaggen zijn veranderd. Een hele dag opnieuw draaien moet dan een no-op zijn, wat crash-herstel triviaal maakt: draai de dag gewoon opnieuw.
# per order, in je ERP-importer
key = storekeeper.order.id # uniek, wordt nooit hergebruikt
display = storekeeper.order.order_number
UPSERT erp_orders SET status, is_paid, is_shipped, totalen WHERE external_id = key
is_paid, is_shipped en status klappen dagen later om. Synchroniseer een naijlend venster (bijvoorbeeld gisteren en vandaag) in plaats van alleen "nieuw sinds de vorige run", en laat de upsert de overlap opvangen.