Ga naar de inhoud
OmniRetailConnect Docs

PIM · API

Synchroniseren

Pagineren, alleen wijzigingen ophalen en een volledig script dat je gegevens up-to-date houdt.

Bekijk als Markdown

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 en meta van een pagina
{
  "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.next bevat dezelfde filters als je verzoek. Je hoeft ze niet te herhalen.
  • links.last is altijd null. Er is geen totaal.
  • Met per_page kies 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:

  1. Noteer het tijdstip waarop je run begint, in Nederlandse tijd.
  2. Vraag de producten op met het filter updated_at, het voorvoegsel >= en het begintijdstip van de vorige run.
  3. Loop de pagina's door en verwerk elk product.
  4. 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):

Wijzigingen sinds de vorige run
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.

pim-sync.php
<?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.next en plakt dat achter het basisadres. Het adres blijft dan altijd hetzelfde.
  • De functie verwerkProduct doet nu alleen een echo. Vervang die door je eigen opslag.

Het script draaien

Draai het eerst met de hand om te zien dat het werkt:

Eerste run
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:

Cron
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.