Recipes / Price list export
CatalogExport the catalog with current base prices: search or drain products, then join the price rows.
The read side of our bulk price tooling.
Omit q and GET /api/products lists the whole catalog. The response is the standard pagination envelope: count is the size of this page, total is the size of the catalog. Loop with start and limit until start reaches total. The limit caps at 200; 200 is a fine page size for a full export.
# first page of the full catalog (no q = match all)
curl -s "https://api.storekeeper.me/api/products?start=0&limit=200" \
-H "Authorization: Bearer $TOKEN"
# then start=200, start=400, ... while start < total
{
"q": "",
"start": 0,
"limit": 200,
"count": 200,
"total": 1418,
"data": [
{ "id": 8211, "sku": "BROOD-VOLKOREN", "title": "Volkorenbrood heel", "product_group_id": 3, "stock": 24.0, "shop_product_id": 9152 },
{ "id": 8214, "sku": "CROIS-ROOMB", "title": "Roomboter croissant", "product_group_id": 4, "stock": 112.0, "shop_product_id": 9155 }
]
}
id is the inner product id, and it is the one the price rows join on. shop_product_id is the shop-level key. Keep both in your export state, but join prices on id.For every catalog page, collect the id values and pass them comma-separated as product_ids to GET /api/product-prices. Chunk the ids to match your page size (up to 200 ids fits comfortably in one call; the endpoint's own limit goes up to 500 rows, and a product can have more than one price row). Join each row back to its product via product_id.
curl -s "https://api.storekeeper.me/api/product-prices?product_ids=8211,8214&limit=100" \
-H "Authorization: Bearer $TOKEN"
{
"start": 0,
"limit": 100,
"count": 3,
"total": 3,
"data": [
{ "id": 30112, "product_id": 8211, "product_price_type_id": 1, "is_sale_price": true,
"ppu": 2.94, "ppu_wt": 3.20, "tax_rate_id": 52, "currency": "EUR", "from_qty": null },
{ "id": 30113, "product_id": 8211, "product_price_type_id": 3, "is_sale_price": false,
"ppu": 1.10, "ppu_wt": 1.20, "tax_rate_id": 52, "currency": "EUR", "from_qty": null },
{ "id": 30140, "product_id": 8214, "product_price_type_id": 1, "is_sale_price": true,
"ppu": 1.28, "ppu_wt": 1.40, "tax_rate_id": 52, "currency": "EUR", "from_qty": null }
]
}
Keep only the rows with is_sale_price: true (that is product_price_type_id = 1); the other type ids are cost and purchase rows you do not want in a customer-facing price list. ppu_wt is the price including VAT, ppu excluding.
product_ids (a stray letter, an empty list after trimming) returns a 400, never a silently-full catalog. That is deliberate: a malformed filter that quietly falls back to "everything" is how exports end up with 1,400 rows where 200 were expected. Treat the 400 as a bug in your id string, fix it, and do not retry as-is.Each sale price row carries a tax_rate_id. One reference call turns that into a percentage; cache the mapping for the whole run.
curl -s "https://api.storekeeper.me/api/tax-rates?country_iso2=NL" \
-H "Authorization: Bearer $TOKEN"
{
"count": 3,
"total": 3,
"data": [
{ "id": 51, "name": "0%", "value": 0, "country_iso2": "NL", "alias": "special_zero" },
{ "id": 52, "name": "9%", "value": 9, "country_iso2": "NL", "alias": "special_low" },
{ "id": 55, "name": "21%", "value": 21, "country_iso2": "NL", "alias": "standard" }
]
}
/api/tax-rates without country_iso2 returns the whole EU registry, Austria first. Always filter.One row per product, joined from the three sources. The recommended columns:
| Column | Source |
|---|---|
sku | product row, sku |
title | product row, title |
| price incl. VAT | sale price row, ppu_wt |
| price excl. VAT | sale price row, ppu |
| VAT percent | tax_rate_id resolved to value via /api/tax-rates |
Use both ppu and ppu_wt as given; never derive one from the other with your own VAT arithmetic, or your export will disagree with the till by a cent. A product without a sale price row is normal (never priced yet): export it with empty price columns rather than dropping it, so the gap is visible.
For refreshes, simply re-run the whole drain. The export is a point-in-time snapshot; there is no delta feed for prices, and at catalog sizes of a few thousand products a full re-run takes seconds. Schedule it nightly, or run it on demand before a print run.
Recepten / Prijslijstexport
CatalogExporteer het assortiment met actuele basisprijzen: zoek of loop producten door, en koppel de prijsregels.
De leeskant van onze bulk-prijstooling.
Laat q weg en GET /api/products geeft de hele catalogus. Het antwoord is de standaard paginering-envelop: count is de grootte van deze pagina, total de grootte van de catalogus. Loop met start en limit tot start bij total is. De limiet is maximaal 200; 200 is een prima paginagrootte voor een volledige export.
# eerste pagina van de volledige catalogus (geen q = alles)
curl -s "https://api.storekeeper.me/api/products?start=0&limit=200" \
-H "Authorization: Bearer $TOKEN"
# daarna start=200, start=400, ... zolang start < total
id is het interne product-id, en daarop sluiten de prijsregels aan. shop_product_id is de winkelsleutel. Bewaar beide in je export, maar koppel prijzen op id.Verzamel per cataloguspagina de id-waarden en geef ze kommagescheiden mee als product_ids aan GET /api/product-prices. Deel de ids op in blokken ter grootte van je pagina (200 ids past prima in één aanroep; de limit van het endpoint gaat tot 500 regels, en een product kan meer dan één prijsregel hebben). Koppel elke regel terug aan zijn product via product_id.
curl -s "https://api.storekeeper.me/api/product-prices?product_ids=8211,8214&limit=100" \
-H "Authorization: Bearer $TOKEN"
Houd alleen de regels met is_sale_price: true (dat is product_price_type_id = 1); de andere type-ids zijn kost- en inkoopregels die je niet in een klantprijslijst wilt. ppu_wt is de prijs inclusief btw, ppu exclusief.
product_ids (een losse letter, een lege lijst na trimmen) geeft een 400, nooit stilletjes de volledige catalogus. Dat is bewust: een kapot filter dat geruisloos terugvalt op "alles" is hoe exports eindigen met 1.400 regels waar er 200 verwacht werden. Zie de 400 als een bug in je id-string, herstel hem, en probeer niet ongewijzigd opnieuw.Elke verkoopprijsregel draagt een tax_rate_id. Eén referentie-aanroep vertaalt dat naar een percentage; cache de mapping voor de hele run.
curl -s "https://api.storekeeper.me/api/tax-rates?country_iso2=NL" \
-H "Authorization: Bearer $TOKEN"
/api/tax-rates zonder country_iso2 geeft het hele EU-register terug, Oostenrijk eerst. Filter dus altijd.Eén regel per product, samengevoegd uit de drie bronnen. De aanbevolen kolommen:
| Kolom | Bron |
|---|---|
sku | productregel, sku |
title | productregel, title |
| prijs incl. btw | verkoopprijsregel, ppu_wt |
| prijs excl. btw | verkoopprijsregel, ppu |
| btw-percentage | tax_rate_id vertaald naar value via /api/tax-rates |
Gebruik ppu en ppu_wt allebei zoals de API ze geeft; leid nooit de één uit de ander af met je eigen btw-rekenwerk, anders wijkt je export een cent af van de kassa. Een product zonder verkoopprijsregel is normaal (nog nooit geprijsd): exporteer het met lege prijskolommen in plaats van het weg te laten, zodat het gat zichtbaar is.
Voor een verversing draai je simpelweg de hele loop opnieuw. De export is een momentopname; er is geen delta-feed voor prijzen, en bij een catalogus van een paar duizend producten duurt een volledige run seconden. Plan hem 's nachts, of draai hem op verzoek vóór een drukgang.