PIM · API
Synchroniseren
Pagineren, alleen wijzigingen ophalen en een volledig script dat je gegevens up-to-date houdt.
Wil je je eigen gegevens gelijk houden met de PIM, dan doe je twee dingen. De eerste keer haal je alle producten op. Daarna haal je met regelmaat alleen op wat is gewijzigd. Op deze pagina staat hoe, met een script dat je kunt overnemen.
Pagineren
Een antwoord bevat één pagina. De PIM zegt niet hoeveel pagina's er zijn, alleen of er een volgende is. Volg daarom links.next tot die null is.
{
"links": {
"first": "https://pim.internetservicebureau.nl/api/v1/products?per_page=500&page=1",
"last": null,
"prev": null,
"next": "https://pim.internetservicebureau.nl/api/v1/products?per_page=500&page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"per_page": 500,
"to": 500
}
}
links.nextbevat dezelfde filters als je verzoek. Je hoeft ze niet te herhalen.links.lastis altijdnull. Er is geen totaal.- Met
per_pagekies je de paginagrootte, tot 1000. Een grotere waarde wordt stilzwijgend 100. - Bij Producten staat niet vast in welke volgorde de producten komen. Schrijf je gegevens daarom zo weg dat een product dat twee keer langskomt niets kapotmaakt: zoek op EAN en werk bij of maak aan.
Alleen wijzigingen ophalen
Met filter[updated_at] vraag je alleen producten op die zijn gewijzigd vanaf een tijdstip. Zie Producten voor wat als wijziging telt. Zo werk je:
- Noteer het tijdstip waarop je run begint, in Nederlandse tijd.
- Vraag de producten op met het filter
updated_at, het voorvoegsel>=en het begintijdstip van de vorige run. - Loop de pagina's door en verwerk elk product.
- Is de run helemaal gelukt, bewaar dan het begintijdstip van deze run voor de volgende keer.
Dat geeft drie afspraken:
- Bewaar het begin, niet het einde. Wijzigt er tijdens je run iets, dan staat dat de volgende keer weer in je resultaat.
- Begin iets eerder. Het script trekt vijf minuten van het bewaarde tijdstip af. Een product dat precies tussen twee runs wijzigt, valt zo niet weg. Daardoor kom je soms een product dubbel tegen. Dat is goed zolang je op EAN bijwerkt.
- Gebruik Nederlandse tijd. Tijdzones worden niet omgerekend, zie de waarschuwing bij Producten. Het script rekent daarom met
Europe/Amsterdam.
Het verzoek dat het script doet, ziet er zo uit (het tijdstip is een voorbeeld):
curl -sS -g "https://pim.internetservicebureau.nl/api/v1/products?per_page=500&filter[updated_at]=%3E%3D2026-03-12T09:55:00" \
-H "Accept: application/json" \
-H "Authorization: Bearer $PIM_API_TOKEN"
Verdwenen producten
Een product dat uit je lijst valt, bijvoorbeeld omdat geen leverancier het meer aanbiedt, komt niet terug als wijziging. Het is er gewoon niet meer. Met alleen wijzigingen merk je dat dus niet.
Draai daarom naast de uurlijkse run af en toe een volledige run, bijvoorbeeld elke nacht. Producten die je dan niet meer ziet, behandel je als niet meer beschikbaar. Doe dat alleen na een run die helemaal is gelukt. Bij een fout heb je maar een deel van de lijst gezien.
Het script
Dit PHP-script doet alles hierboven. De eerste keer haalt het alle producten op. Daarna haalt het alleen wijzigingen op. Met --volledig dwing je een volledige run af. Het tijdstip van de laatste geslaagde run staat in pim-laatste-run.txt naast het script.
<?php
// pim-sync.php
//
// Haalt producten op uit de PIM. De eerste keer (of met --volledig) alles,
// daarna alleen de producten die sinds de vorige keer zijn gewijzigd.
//
// Gebruik: PIM_API_TOKEN=<jouw-token> php pim-sync.php [--volledig]
// Elk uur: 0 * * * * PIM_API_TOKEN=<jouw-token> php /pad/naar/pim-sync.php
const BASE_URL = 'https://pim.internetservicebureau.nl/api/v1';
const STATE_FILE = __DIR__.'/pim-laatste-run.txt';
const PER_PAGE = 500;
// We beginnen iets eerder dan de vorige run. Een product dat precies tussen
// twee runs wijzigt, gaat zo niet verloren.
const OVERLAP_SECONDS = 300;
// De PIM leest de tijd in filter[updated_at] als Nederlandse tijd.
const TIMEZONE = 'Europe/Amsterdam';
function verwerkProduct(array $product): void
{
// Vervang dit door je eigen opslag: zoek het product op EAN en maak het
// aan of werk het bij. Doe het zo dat dezelfde EAN twee keer verwerken
// niets kapotmaakt.
echo $product['ean13'], ' ', $product['name'], PHP_EOL;
}
function haalOp(string $url, string $token): array
{
for ($poging = 1; $poging <= 3; $poging++) {
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
"Authorization: Bearer {$token}",
],
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
if ($status === 200 && is_string($body)) {
return json_decode($body, true, 512, JSON_THROW_ON_ERROR);
}
// 4xx: de aanvraag zelf klopt niet. Opnieuw proberen helpt dan niet.
if ($status >= 400 && $status < 500) {
$melding = json_decode((string) $body, true)['message'] ?? 'onbekende fout';
throw new RuntimeException("De PIM antwoordde {$status}: {$melding}");
}
// Geen antwoord of een serverfout (5xx): even wachten en opnieuw.
sleep($poging * 5);
}
throw new RuntimeException('De PIM bleef niet antwoorden. Probeer het later opnieuw.');
}
$token = getenv('PIM_API_TOKEN');
if (! $token) {
fwrite(STDERR, "Zet eerst de omgevingsvariabele PIM_API_TOKEN.\n");
exit(1);
}
$tijdzone = new DateTimeZone(TIMEZONE);
// Het starttijdstip van deze run. Dat bewaren we aan het einde, niet de eindtijd.
$start = new DateTimeImmutable('now', $tijdzone);
$volledig = in_array('--volledig', $argv, true) || ! is_file(STATE_FILE);
$query = ['per_page' => PER_PAGE];
if (! $volledig) {
$vorigeRun = new DateTimeImmutable(trim(file_get_contents(STATE_FILE)), $tijdzone);
$sinds = $vorigeRun->modify('-'.OVERLAP_SECONDS.' seconds');
$query['filter[updated_at]'] = '>='.$sinds->format('Y-m-d H:i:s');
}
$volgende = http_build_query($query, '', '&', PHP_QUERY_RFC3986);
$aantal = 0;
// Blijf pagina's ophalen tot er geen volgende pagina meer is.
while ($volgende !== null) {
$pagina = haalOp(BASE_URL.'/products?'.$volgende, $token);
foreach ($pagina['data'] as $product) {
verwerkProduct($product);
$aantal++;
}
// We gebruiken alleen het queryonderdeel van links.next en plakken het achter
// ons eigen adres. Het filter en het paginanummer zitten daar al in.
$link = $pagina['links']['next'] ?? null;
$volgende = $link === null ? null : parse_url($link, PHP_URL_QUERY);
}
// Pas na een geslaagde run bewaren we het tijdstip.
file_put_contents(STATE_FILE, $start->format('Y-m-d H:i:s'));
echo ($volledig ? 'Volledige run' : 'Wijzigingen').": {$aantal} producten verwerkt.", PHP_EOL;
- Bij een fout van 4xx (bijvoorbeeld een ongeldig token) stopt het script direct en bewaart het geen tijdstip.
- Bij een serverfout of geen antwoord wacht het en probeert het tot drie keer.
- Het gebruikt alleen het queryonderdeel van
links.nexten plakt dat achter het basisadres. Het adres blijft dan altijd hetzelfde. - De functie
verwerkProductdoet nu alleen eenecho. Vervang die door je eigen opslag.
Het script draaien
Draai het eerst met de hand om te zien dat het werkt:
PIM_API_TOKEN='<jouw-token>' php pim-sync.php
Daarna kun je het laten draaien met cron, bijvoorbeeld elk uur voor wijzigingen en elke nacht volledig:
0 * * * * PIM_API_TOKEN='<jouw-token>' php /pad/naar/pim-sync.php
30 3 * * * PIM_API_TOKEN='<jouw-token>' php /pad/naar/pim-sync.php --volledig
Vraag zuinig
Vraag niet vaker dan nodig. Voor de meeste koppelingen is een keer per uur genoeg. Haal liever grotere pagina's op (tot 1000 producten) dan veel kleine, en vraag de pagina's na elkaar, niet tegelijk.