Storefront-integratie met de Flow API

Bouw je eigen klantgerichte voertuig-zoekwebsite op de EuroStocks Flow API, zoals de referentie-storefront op https://import-demo.eurostocks.com.

Vereisten
  • Een Flow-serviceaccount van EuroStocks (toegang aanvragen).
  • Een eigen back-end, in elke taal (PHP, .NET, Java, Node, …): de API is alleen server-to-server bereikbaar.

1. Architectuur — de ene harde regel

De browser krijgt nooit een token. De API is uitsluitend server-to-server bereikbaar: geen CORS, geen tokens in de browser of in URL's. Je bouwt daarom altijd een eigen back-end-proxy:

TEXT
browser (no token)
   │  same-origin
   ▼
your back-end proxy ──▶ Authentik
   │                    (token)
   │  HTTPS + bearer token
   ▼
Flow API
  • De browser praat same-origin met jouw proxy; alleen de proxy kent de inloggegevens en het token.
  • De proxy stuurt uitsluitend een vaste, kleine set GET-endpoints door (§3) en zet zelf de Authorization: Bearer <token>-header erop. Verwijder een Authorization-header en een merchant-parameter van de browser.
  • Inloggegevens en tokens horen alleen in de serveromgeving of het geheugen: nooit in logs, responses of URL's.

2. Authenticatie

Je ontvangt van EuroStocks een serviceaccount (Authentik, machine-to-machine) met: de API-basis-URL (https://flow-api.eurostocks.com), de token-URL, een client-id en de gebruikersnaam + het app-wachtwoord van het serviceaccount.

Een token vraag je aan met OAuth2 client_credentials: een POST naar de token-URL met de formuliervelden grant_type, client_id, username, password en scope (openid memberships). Het antwoord bevat access_token en expires_in (seconden).

BASH
curl -s https://auth.eurostocks.com/application/o/token/ \
  -d grant_type=client_credentials \
  -d client_id="$FLOW_CLIENT_ID" \
  -d username="$FLOW_USERNAME" \
  -d password="$FLOW_APP_PASSWORD" \
  -d scope="openid memberships"

Dit is Authentik's serviceaccount-variant van client_credentials: hij gebruikt bewust username + password in plaats van het standaardpaar client_id + client_secret. "Corrigeer" dit niet naar de standaardvorm, want dan faalt de authenticatie.

Merchant-koppeling: het serviceaccount is lid van precies één merchant: jouw storefront. De Flow API leidt die zelf af; stuur dus nooit een merchant-parameter mee.

Tokenbeheer (verplicht om goed te doen):

  • Cache het token in het geheugen en hergebruik het tot ~30 seconden voordat het verloopt; vraag dan een nieuw aan.
  • Laat gelijktijdige verzoeken één aanvraag delen (single-flight): vraag niet per request een token aan.
  • Krijg je toch een 401 van de API: vraag één keer een vers token aan en herhaal het verzoek één keer.
  • Aanrader: vraag het eerste token aan bij het opstarten van je server, zodat de eerste bezoeker er niet op wacht.

3. Het API-oppervlak

Alle paden zitten onder /api/v1 (oude paden zonder versie geven 404). Alles is GET. Let op: het storefront/-segment zit alléén op search en het voertuigdetail.

Endpoint Doel
GET /api/v1/storefront/search Zoeken: hits, totaal, facet-tellingen (§4)
GET /api/v1/storefront/vehicles/{id} Voertuigdetail (volledige specificaties, galerij)
GET /api/v1/attributes Het contract: filters, sorteringen, labels, groepen (§5)
GET /api/v1/catalog Referentietaxonomie: merk → modelgroep → model, met canonieke URL-slugs (§6)

Je proxy stuurt alleen deze vier door.

Getypeerde client: GET /api/v1/openapi.json levert het OpenAPI 3.1-document van de API (met hetzelfde token, zonder extra recht). Genereer je client daaruit in plaats van velden over te typen (bijvoorbeeld met openapi-typescript) en gebruik de operaties met de tag storefront: precies de vier hierboven. Haal het op bij je build, niet vanuit de browser. De labels in het document zijn de standaardteksten; de actuele staan in /api/v1/attributes.

Wat je storefront aanbiedt. Je merchant heeft instellingen die de API afdwingt; ze gelden voor elke zoekopdracht, ook voor de facet-tellingen:

  • Minimaal registratiejaar: elke zoekresponse bevat merchant.min_first_registration_year. Is het gezet, dan geeft zoeken nooit oudere auto's en levert een gekozen jaar daaronder nul resultaten. Begrens je jaarfilter erop; null betekent geen ondergrens.
  • Merken en modellen: een storefront kan beperkt zijn tot bepaalde merken of modellen uitsluiten. De catalog (§6) bevat dan alleen wat je aanbiedt; een merk of model daarbuiten geeft nul resultaten, en in het zoekvak wordt het niet toegepast (outside_scope, §4).
  • Prijsweergave: merchant.price_display (final, lease of both) bepaalt welke prijzen en prijsfilters het contract adverteert.

Deze grenzen gelden voor zoeken: een directe link naar een voertuig-id werkt ook voor een auto daarbuiten.

Rate limiting (aanrader): al het bezoekersverkeer bereikt de Flow API via jouw proxy met één serviceaccount. De API kan individuele bezoekers dus niet onderscheiden, en misbruik door één bezoeker telt als verkeer van jou. Limiteer daarom per bezoekers-IP in je eigen proxy, op de doorgestuurde /api/*-paden. Een verstandige instelling: een token-bucket met een burst van ~10 requests en ~1–2 requests/seconde aanhoudend per IP (± 60–120/min). Dat zit ruim boven het drukste legitieme gedrag (snel filters wisselen piekt kort op enkele requests per seconde), maar stopt geautomatiseerde excessen. Antwoord bij overschrijding met 429 + een Retry-After-header. Dit hoeft niet per se in de proxy: een edge- of CDN-laag vóór je proxy (bijv. Bunny.net of Cloudflare) kan dezelfde per-IP-limiet afdwingen, vaak eenvoudiger te beheren, en houdt het verkeer al buiten de deur. De gecachte naslagdocumenten (attributes, catalog) raken je limiet nauwelijks: die beantwoordt je proxy uit eigen geheugen.

Daarnaast hanteert de Flow API zelf een rate limit per merchant, gebaseerd op het afgenomen abonnement. Overschrijd je die, dan krijgt je proxy een 429 (code: "RATE_LIMITED") met een Retry-After-header (seconden): respecteer die en probeer niet agressief opnieuw. Je eigen per-IP-limiter is dus ook zelfbescherming: hij voorkomt dat één bezoeker (of bot) het quotum van je hele storefront opmaakt.

Cache-advies: attributes en catalog zijn naslagdocumenten. Haal ze server-side op, bewaar ze in het geheugen en hervalideer met If-None-Match: beide leveren een ETag en antwoorden met een goedkope 304 zolang er niets veranderd is. De catalog stuurt Cache-Control: private, max-age=30; houd je eigen TTL kort, zodat een wijziging in je aanbod snel zichtbaar is. Stuur ze gecomprimeerd naar de browser. Zoekopdrachten stuur je per request door.

4. Zoeken

Verzoekparameters (querystring op /api/v1/storefront/search):

  • q — het zoekvak: herkende woorden worden filters (zie Het zoekvak hieronder).
  • description — "Uitvoering / omschrijving bevat": woorden die altijd als tekst worden gezocht, nooit als filter (trekhaak als woord, AMG Line), samen met de tekstwoorden van q in één zoekvraag; maximaal 100 tekens.
  • page, per_page — paginering. De server begrenst per_page op maximaal 50 (standaard 24) en page op maximaal 200; een waarde buiten bereik wordt stil geklemd, nooit een foutmelding. Neem de paginaberekening daarom over uit de response (page, per_page) en begrens je eigen paginering op pagina 200.
  • sort — een sorteertoken uit het contract (§5); codeer geen sorteringen hard. De standaardsortering is de eerste in de lijst (prijs, laag → hoog). Het laatste token, _text_match:desc ("Beste match"), rangschikt op hoe goed de zoektekst matcht; zonder tekst valt het terug op prijs.
  • facets — kommagescheiden lijst velden waarvan je tellingen wilt (bijv. facets=make,fuel,transmission,year); een herkende lijst vervangt de standaardset. Vraag alleen aan wat je toont. Blader, sorteer of wissel je de paginagrootte met ongewijzigde filters, stuur dan facets=none: geen tellingen (facet_counts: []).
  • Filtervelden — elk filter in het contract adverteert zijn eigen queryparameters (params); gebruik die namen letterlijk en leid zelf geen namen af. Enum-velden gaan als kommagescheiden contractcodes (fuel=PETROL,HYBRID; de codes staan per filter in het contract), bereiken als het geadverteerde paar (price_min/price_max, mileage_min/mileage_max, seats_min/seats_max).
    • year_min / year_max is het registratiejaar van–tot (beide inclusief); year is een exacte meerkeuze (year=2024,2025).
    • make, model, model_group filteren de taxonomie; model vindt beide spellingen die de feed gebruikt (de catalog-sleutel en het label).
  • Een allowlist aan de API-kant bepaalt welke parameters geaccepteerd worden; een prijs- of leaseparameter die je storefront niet adverteert, is een 400.

Het zoekvak (q)

Herkende woorden worden gewone filterparameters. De overige woorden zoeken als tekst in de modelregel van de dealer (titel en model): elk woord moet matchen, met tolerantie voor tikfouten en schrijfvarianten (c200 = c 200, s line = sline). Een filter dat je zelf meestuurt, wint altijd.

Herkend Voorbeelden Wordt
Merk bmw, volkswagen, vw, mercedes, benz make
Model, modelgroep bmw x5, golf, mercedes glc model / model_group (+ make)
Brandstof benzine, diesel, elektrisch, ev, hybride, plug-in, phev fuel, hybrid_plugin
Transmissie automaat, dsg, handgeschakeld transmission
Carrosserie suv, stationwagen, combi, cabrio category
Aandrijving 4x4, awd, 4matic, quattro, xdrive, 4motion drive_type
Uitrusting trekhaak (afneembare trekhaak / vaste trekhaak: alleen die soort), panoramadak, leer, stoelverwarming, navi, hud, cruise control, acc, led, camera, pdc, en de Nederlandse naam van elke optie in het contract het filter van die optie
Kleur zwart, zwarte golf; naast een interieurwoord de interieurkleur: zwart leer, beige interieur color; interior_color
Lak metallic, matte, mat zwart metallic, matte_color
Eerste eigenaar eerste eigenaar, 1e eigenaar previous_owners_max=1
Schadevrij geen schade, zonder schadeverleden, schadevrij accident_damaged=false (zoals de dealer het opgeeft)
Type- en motorcodes 530d (BMW 530 + diesel), 530e (plug-in hybride), 520da (+ automaat), x5 xdrive30d (diesel + vierwielaandrijving), tdi (diesel), tsi / tfsi (benzine), tfsi e / ehybrid (plug-in hybride) model + fuel / drive_type / transmission
Getallen met eenheid of voorzetsel vanaf 2020, 2018-2021, onder €20.000, max 50.000 km, 150 pk, 110 kw, tot 300 per maand year_min/year_max, price_*, mileage_*, power_pk_* / power_*, lease_*
  • Model zonder merk: een modelnaam van 4+ tekens die bij één merk hoort, werkt ook zonder merk (golf = Volkswagen Golf); kortere namen (x5, a4) alleen met het merk erbij. Een code die bij één merk hoort (tfsi, quattro → Audi; xdrive → BMW; 4motion → Volkswagen) laat ook een kort model van dat merk meetellen: a4 tfsi = Audi A4 + benzine.
  • Uitvoeringen blijven tekst: gti, gtd, gte, rs, amg, m sport, r-line, s-line, st-line (een golf gte is één uitvoering, niet elke plug-in Golf), en ook grijs kenteken.
  • Getallen: een kaal jaartal is dat ene jaar (2020). pk en kW zonder voorzetsel betekenen "vanaf"; km, € en een maandbedrag "tot". Een getal zonder eenheid (tot 20000) wordt een prijs zolang je storefront prijzen toont; tot 20000 km is kilometerstand. Motorinhoud (1.5, 2,0) blijft tekst.
  • Tikfouten: een duidelijke tikfout telt als het bedoelde woord: één letter naast een merk, een woord uit deze lijst of een model van een al bekend merk, bij 5+ letters, zonder cijfer en met maar één betekenis (mercdes, panoramdak, vw tiguen). De chip noemt het echte woord, from het getypte.
  • Verbindingswoorden die overblijven (en, of, met, een, de, het, in, op, van, und, mit) vallen weg.
  • interpret=false zoekt q als platte tekst: alleen schrijfvarianten, tikfouttolerantie en merkaliassen (volkswagen = VW), geen filters en geen interpretation-blok.

Het interpretation-blok. Elke response op een niet-lege q bevat interpretation:

  • applied — de chips. Per chip: params precies zoals je ze zelf zou sturen (enumcodes en true als tekst, bereikgrenzen als getal), label / label_nl en from (de getypte woorden).
  • text — de woorden die als tekst zijn gezocht (null = geen).
  • not_applied — herkend maar niet toegepast, met een reason:
    • conflicts_with_filter: je verzoek zet die parameter al op een andere waarde; suggestions bevat de vervanging;
    • outside_scope: een merk of model buiten je aanbod (§3);
    • not_available: een prijs- of leasegrens die je storefront niet toont;
    • ambiguous: een getal zonder eenheid dat hier geen prijs kan zijn (bijv. max 300); suggestions biedt de lezingen aan, de woorden blijven in text.

Een zoekopdracht en de (ingekorte) response:

BASH
curl -s "https://flow-api.eurostocks.com/api/v1/storefront/search?q=bmw%20530d%20tot%2040000&facets=fuel" \
  -H "Authorization: Bearer $FLOW_TOKEN"
JSON
{
  "found": 37, "page": 1, "per_page": 24,
  "merchant": { "id": "import-demo", "name": "Import Demo", "price_display": "final",
                "min_first_registration_year": 2018 },
  "facet_counts": [{ "field_name": "fuel", "counts": [{ "value": "DIESEL", "count": 37 }] }],
  "hits": [{ "id": "900001", "make": "BMW", "model": "530", "make_slug": "bmw",
             "model_slug": "530", "final_price": 38950, "vat_scheme": "nl_vat_21",
             "image_url": "https://…", "image_count": 12 }],
  "interpretation": {
    "applied": [
      { "params": { "make": "bmw" }, "label": "BMW", "label_nl": "BMW", "from": "bmw" },
      { "params": { "model": "530" }, "label": "530", "label_nl": "530", "from": "530d" },
      { "params": { "fuel": "DIESEL" }, "label": "Diesel", "label_nl": "Diesel", "from": "530d" },
      { "params": { "price_max": 40000 }, "label": "Price up to € 40,000",
        "label_nl": "Prijs tot € 40.000", "from": "tot 40000" }
    ],
    "text": null,
    "not_applied": []
  }
}

Zo verwerk je het: neem applied over in je filterstatus en URL zonder opnieuw te zoeken (de response bevat de resultaten al), zet text terug in het zoekvak en toon de not_applied-meldingen. Pagina 2 stuurt dan de filters plus q=<text> en geeft hetzelfde resultaat. Laat de bezoeker de hele getypte zin zien en bewerken (de referentie-storefront doet dat): verwijdert de bezoeker een chip, haal dan de woorden uit zijn from uit het zoekvak; verstuurt de bezoeker de zin opnieuw, haal dan eerst de chips van de vorige zin weg (zelf gekozen filters blijven, behalve een waarde die ook uit de zin kwam). Zoek bij verzenden (Enter of zoekknop), niet bij elke toetsaanslag: dat spaart je rate-limitquotum en voorkomt flikkerende resultaten.

Responsevorm (de velden waar de referentie-storefront op bouwt):

  • found — het exacte totaal; hits — de resultaten van deze pagina.
  • Per hit o.a.: id, title, make, model, make_slug, model_slug, final_price, vat_scheme (de btw-context van de prijs), optioneel lease_price (€/maand, met bijbehorende lease_terms), image_url, image_urls (de eerste ~5 galerij-URL's) en image_count.
  • facet_counts — een lijst van { field_name, counts: [{ value, count }] } per aangevraagd veld.
  • merchant — { id, name, price_display, min_first_registration_year }: de instellingen van je storefront (§3).
  • interpretation — alleen bij een niet-lege q (zie hierboven).
  • Let op de naamgeving: zoekhits gebruiken snake_case (make_slug, final_price), het voertuigdetail camelCase (makeSlug, finalPrice, leaseTerms); houd daar in je mapping rekening mee.

Twee valkuilen:

  1. Facet-tellingen zijn kort gecachet. Tellingen worden exact berekend, maar server-side kort (~60 s) gecachet, en kunnen dus iets afwijken van de werkelijke resultaten van dat moment. Je mag ze tonen; houd er alleen rekening mee dat een getoonde telling en het exacte found-totaal niet altijd precies op elkaar aansluiten.
  2. Prijsweergave volgt het contract. Welke prijs je toont, bepaalt het contract (price_display: final / lease / both); een lease-only storefront levert bijvoorbeeld geen final_price. Toon bij elke prijs de bijbehorende btw-context (vat_scheme) en bij een maandbedrag de leasevoorwaarden (lease_terms / leaseTerms). De API levert uitsluitend deze publieke projectie; er is geen kostenopbouw of marge-informatie, en probeer die ook niet af te leiden.

Afbeeldingen — CDN-representaties en het AVIF-formaat

Alle afbeeldings-URL's die de API levert (image_url, image_candidates, image_urls en de galerij van het voertuigdetail) zijn representatie-URL's van het mobile.de-CDN (img.classistatic.de), in de vorm …?rule=mo-<breedte>.jpg. Gebruik uitsluitend de geleverde URL's en leid zelf geen URL's of breedtes af: welke representaties bestaan, is kennis van de feed, en de API levert per doel al de juiste maat (zoekkaart, galerij, thumbnails).

Er is één gedocumenteerde bewerking op een geleverde URL (zie ook de veldbeschrijvingen in het OpenAPI-document): laat je de extensie .jpg uit de rule-parameter weg (?rule=mo-1024 in plaats van ?rule=mo-1024.jpg), dan levert het CDN dezelfde afbeelding als AVIF, doorgaans 40–55 % kleiner. Twee belangrijke kanttekeningen:

  1. Het formaat ligt vast, er is geen content-negotiation. De extensieloze URL levert altijd AVIF (ongeacht de Accept-header, er is geen Vary). Wissel dus nooit kaal de URL om: een browser zonder AVIF-ondersteuning krijgt dan een niet-decodeerbaar bestand. Gebruik altijd een <picture> met een getypeerde source en de .jpg-URL als fallback:

    HTML
    <picture>
      <source type="image/avif" srcset="https://img.classistatic.de/…?rule=mo-1024">
      <img src="https://img.classistatic.de/…?rule=mo-1024.jpg" alt="…">
    </picture>

    Houd og:image en andere metadata voor scrapers op de .jpg-vorm: AVIF-ondersteuning is daar onbetrouwbaar.

  2. Dit is waargenomen CDN-gedrag van een derde partij (geverifieerd 2026-08-06), geen garantie die deze handleiding kan geven. De type-gate hierboven begrenst het risico: valt de AVIF-variant ooit weg, dan faalt hooguit één request en kun je met een error-handler op de <img> terugvallen op de .jpg-source (verwijder de AVIF-sources en de browser kiest opnieuw).

5. Het attributes-contract — bouw je UI hierop

GET /api/v1/attributes is het contract dat je filters, sorteeropties, labels en lay-out aanstuurt. Render vanaf dit contract, niet vanaf hardgecodeerde lijsten: dan volgt je site automatisch wijzigingen in het aanbod.

  • version — controleer op "v1". Wijkt hij af, toon dan een (interne) waarschuwing: je filters kunnen verouderd zijn.
  • filters / sorts — welke filtervelden en sorteertokens er bestaan, inclusief de soort (enum, range, int, bool, date_range, text; het bredere attributenregister kent ook tri_state), de bijbehorende queryparameters (params) en per enum de toegestane waardecodes. Negeer onbekende toekomstige soorten netjes (niet renderen) in plaats van erop te breken. De geadverteerde parameters horen bij jouw storefront: leaseparameters alleen als je lease toont, en vat_reclaimable verdwijnt bij een storefront zonder margeauto's.
  • attributes — het register van uitrusting en specificaties. Elk attribuut is een zoekfilter; de waarden staan alleen in het voertuigdetail (attributes), niet op zoekhits.
  • Labels: gebruik label_nl / group_nl / disclaimer_nl met de Engelse velden als terugval. De formuleringen dragen betekenis (bijv. btw-verrekenbaarheid): verzin geen eigen bewoordingen. Codes zijn stabiel, labels niet: EuroStocks kan een label corrigeren zonder nieuwe release.
  • groups — de secties waarin je filters en de voertuigdata op de detailpagina groepeert.
  • response_only-velden + detail_field-koppelingen — welke responsevelden je als datarijen op de detailpagina toont; een veld met values (zoals vat_scheme) vertaal je via die codetabel.
  • Disclaimers: velden met een disclaimer (zoals CO₂, een ≈-schatting) toon je mét die disclaimer. Bijzonderheid CO₂: bij elektrische voertuigen levert de feed co2_emissions: 0; onderdruk de rij daar.

6. Catalog en URL's

GET /api/v1/catalog levert de referentietaxonomie (merk → modelgroep → model) inclusief canonieke, opgeslagen URL-slugs, beperkt tot wat jouw storefront aanbiedt (§3): bouw je merk- en modelkeuzelijsten erop, dan volgen ze je aanbod vanzelf.

  • Maak nooit zelf slugs. Inkomende URL-slugs zet je via de catalog om naar filterwaarden; uitgaande links bouw je met de slugvelden die op de hit (make_slug / model_slug) en het detail (makeSlug / modelSlug) meekomen.
  • De referentie-storefront gebruikt paden als /{make}/{model} (lijst) en /{make}/{model}/{id} (detail): een goed deelbaar patroon om over te nemen (wat je ervan laat indexeren: zie §7).
  • Escape alles. Merk, model en id uit de URL zijn door de bezoeker te manipuleren: HTML-escape ze overal waar je ze in de pagina schrijft (titels, meta-tags). Laat het voertuig-id zijn ruwe URL-encoding houden wanneer je het in het upstream-pad invoegt.

7. SEO en crawlers

De voorraad is groot en wisselt snel. Je wilt Google dus wél op de stabiele lijstpagina's, maar niet op honderdduizenden kortlevende detailpagina's (dode links in de zoekresultaten, dunne content, en elke crawl kost jouw quotum).

  • Indexeer alleen de taxonomie-laag: de homepage, de ongefilterde resultatenpagina en de schone merk- en modelpaden (/{make}, /{make}/{model}). Alleen die URL's horen in je sitemap.xml (goed te genereren uit de catalog).
  • Detailpagina's: noindex, follow via een meta-tag in de HTML, niet via robots.txt: Google moet de pagina kunnen ophalen om de noindex te zien. Links erheen blijven volgbaar, dus je lijstpagina's houden hun linkwaarde.
  • Query-URL's (filters, sortering, paginering) geef je een canonical naar het schone pad zonder query; anders wordt gefacetteerd zoeken een crawl-val van eindeloze filtercombinaties.
  • robots.txt: Disallow: /api/ (crawlen daarvan kost alleen quotum) en verwijs naar je sitemap.
  • Verkochte of verdwenen auto's: een echte 404 of 410 (of een redirect naar de modellijst), nooit een lege pagina met status 200. Soft-404's op schaal schaden het hele domein, en óók met noindex blijven bezoekers via gedeelde links binnenkomen. De API antwoordt dan zelf 404 (§8).
  • Canonical en og:url zijn absolute URL's op je eigen origin.

Dit is de juiste standaard voor een storefront op de volledige importvoorraad. Toon je maar een kleine, stabielere deelvoorraad (honderden auto's, langere statijd), dan kunnen detailpagina's wél mee in de index; de overige regels blijven gelden.

8. Foutcodes

Fouten zijn JSON {error, code?}. Reageer op de machinecode code, niet op de tekst in error:

Status code Betekenis
401 AUTH_REQUIRED Token ontbreekt of is ongeldig: vraag één keer een vers token aan en herhaal (§2).
403 NO_ROLES Het token hoort bij geen enkele merchant.
403 MODULE_FORBIDDEN Het serviceaccount heeft geen recht op de storefront-API.
403 MERCHANT_FORBIDDEN Er is een merchant-parameter meegestuurd die niet bij je token hoort: stuur nooit merchant.
400 MERCHANT_REQUIRED Het serviceaccount hoort bij meer dan één merchant; neem contact op met EuroStocks.
429 RATE_LIMITED Rate limit overschreden: wacht de seconden uit Retry-After (§3).

Zonder code: een ongeldige parameter is 400 {error} (bijv. een prijsparameter die je storefront niet adverteert), een onbekend voertuig-id 404 {error: "vehicle not found"}, een onbekend /api/*-pad 404 {error: "unknown endpoint"} en een onverwachte storing 500 {error: "internal error"}.

9. Startchecklist

  1. Ontvang van EuroStocks: de API-basis-URL, de token-URL, een client-id en de inloggegevens van het serviceaccount.
  2. Bouw de proxy: tokenbeheer (§2) en uitsluitend de endpoints uit §3 doorsturen; verwijder merchant en de Authorization-header van de browser; rate-limit per bezoekers-IP; niet-toegestane /api/*-paden → 404.
  3. Laad en cache attributes en catalog; controleer version === "v1".
  4. Render de zoekpagina en filters vanaf het contract; begrens het jaarfilter op merchant.min_first_registration_year; bouw URL's met catalog-slugs.
  5. Verwerk het interpretation-blok van het zoekvak: chips overnemen, text terug in het vak, meldingen tonen (§4).
  6. Toon de prijzen die het contract adverteert (price_display), met vat_scheme, leasevoorwaarden en de contractlabels en -disclaimers erbij.
  7. Regel de crawlerkant (§7): een sitemap met alleen lijstroutes, noindex op detailpagina's, Disallow: /api/ en een echte 404 voor verkochte auto's.
  8. Verifieer: geen token of inloggegevens in de browser, logs of URL's; 401 → één keer vernieuwen en herhalen; de foutcodes uit §8 afgehandeld, bij 429 Retry-After gerespecteerd.
Volgende stappen