Katalog, Preise und Produktdarstellung
Bauen Sie Produktlisten und Produktdetails aus öffentlichen Storefront-Daten. Die Suche liefert Treffer und Facetten; Produktdaten, Preise und Verfügbarkeit laden Sie danach getrennt.
Wenn Produktdaten für Variantenachsen, Zubehör, Serviceoptionen oder Mengen-/Einheitenmodelle vorbereitet werden, stimmen Sie die Pflege mit Produktmodellierung: Entscheidungshilfe ab. Die Storefront rendert das gepflegte Modell und erfindet keine eigenen Produktbeziehungen.
Voraussetzungen
Der öffentliche Storefront-Kontext setzt voraus, dass Workspace für den aktuellen Host eine aktive Storefront auflösen kann. Ein laufender Server reicht dafür nicht aus.
Prüfen Sie vor dem ersten API-Aufruf:
- Der Zielmandant wurde mit Tenant Init vorbereitet. Für Katalog und Produktdarstellung müssen mindestens Lokalisierung und PIM initialisiert sein; für Produkt-Lifecycle und Readiness muss die systemweite Workflow-Library bereit sein. System > Betrieb > Betriebsbereitschaft zeigt keine blockierenden systemweiten Hinweise.
- Eine öffentliche Site ist angelegt, aktiv und über eine verifizierte
cms_public-Tenant-Domain an den erwarteten Host gebunden. Nutzen Sie dafür Storefront- und Site-Domains einrichten. - Die Site ist einem Sales Channel zugeordnet.
- Der Sales Channel hat ein Commerce-Profil, eine Preisliste, Locale- und Währungskonfiguration.
- Mindestens ein Produkt ist für den Sales Channel veröffentlicht, wenn die erste Liste nicht leer bleiben soll.
Administratoren richten Sites unter CMS > Sites ein. Die technischen Site-Abläufe stehen in Sites und CMS. Produktpflege, Preise und Veröffentlichung stehen in Quickstart: Produkt veröffentlichen.
Storefront-Kontext laden
Laden Sie zuerst den Storefront-Kontext:
GET /api/v1/public/v1/storefront/context
Accept-Language: de-DEBeispielantwort:
{
"siteId": "uuid",
"siteSlug": "shop",
"salesChannelId": "uuid",
"locale": "de-DE",
"currency": "EUR",
"taxMode": "gross",
"guestCheckoutAllowed": true,
"loginRequiredForPrices": false,
"loginRequiredForAvailability": false,
"priceDisplayMode": "visible",
"availabilityDisplayMode": "available",
"quoteAllowed": true,
"companyAccessEnabled": true,
"flow": "guest",
"allowedPaymentMethods": ["prepayment", "paypal"]
}Nutzen Sie die Antwort als Storefront-Kontext für Locale, Währung, Steueranzeige, Preis- und Verfügbarkeitsanzeige, Gastkauf, Login-Hinweise und Zahlarten. Senden Sie keine autoritativen tenantId-, siteId-, salesChannelId-, customerGroupId- oder priceListId-Parameter.
Locale einer mehrsprachigen Seite weitergeben
Wenn die aktuelle CMS-Seite bereits eine Sprachvariante gewählt hat, senden Sie diese Locale als Accept-Language an alle zugehörigen öffentlichen Storefront-Reads. Verwenden Sie für Storefront-Kontext, Public Catalog Search, Product Summary, Product Detail und ProductRoute denselben Wert:
const pageLocale = document.documentElement.lang || "de-DE";
const headers = { "Accept-Language": pageLocale };
const context = await fetch("/api/v1/public/v1/storefront/context", { headers });
const products = await fetch("/api/v1/public/v1/catalog/products/summary", {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ variantIds })
});Workspace verhandelt den Header nur gegen serverseitige Angebote: die konfigurierten Site-/Sales-Channel-Defaults und tatsächlich öffentliche, gelistete Produktprojektionen desselben Tenants und Sales Channels. Ein regionaler Wunsch wie en-GB kann auf eine vorhandene Projektion en fallen. Fehlt ein passendes Angebot, verwendet Workspace weiterhin Site-Default, Sales-Channel-Default und danach de-DE.
Übernehmen Sie den Wert nicht aus einem frei geratenen URL-Segment. Nutzen Sie die Locale, die die Seite selbst in html[lang] oder ihrem kontrollierten Seitenkontext ausweist. Leiten Sie aus der Sprache weder Währung, Land, Preisliste, Steuerkontext noch Checkout-Regeln ab. Der Server hält diese Werte im vorhandenen Storefront-Scope autoritativ. Sprachabhängige Antworten senden Vary: Accept-Language, damit Caches Sprachvarianten nicht vermischen.
Behandeln Sie Sprache, Land und Markt als getrennte Teile desselben Storefront-Kontexts:
- Der Sales Channel und sein Commerce-Profil beschreiben den Markt, zum Beispiel Land, Standardwährung, Steueranzeige, Preisliste und erlaubte Checkout-Regeln.
- Die Locale beschreibt Sprache und regionale Schreibweise, zum Beispiel
de-DE,de-AT,fr-CHoderen-CH. - ProductRoute löst den sichtbaren Produktpfad im aktuellen Site-, Sales-Channel- und Locale-Kontext auf.
- Die sichtbare Preisdarstellung nutzt immer Locale und Währung aus diesem Scope. Leiten Sie Währung, Land oder Preisliste nicht aus der Sprache ab.
- Die Währung aus Sales Channel, Commerce-Profil und Preisliste muss für den Mandanten als aktive Tenant-Währung freigeschaltet sein. Der Server nutzt den Währungskatalog für Symbol und Nachkommastellen; er rechnet Verkaufspreise nicht automatisch in andere Währungen um.
Dadurch können zwei Länder dieselbe Sprache nutzen und trotzdem unterschiedliche Währungen, Steuerregeln oder Preislisten haben. Ebenso kann ein Land mehrere Sprachen nutzen, ohne dass sich dadurch Markt, Preisliste oder Checkout-Regeln ändern. Rendern Sie dynamische Preise im Client mit der Locale und Währung aus /storefront/context, zum Beispiel mit Intl.NumberFormat(locale, { style: "currency", currency }). Verwenden Sie keine festen Regeln wie „Deutsch = EUR“ oder „Währung steht immer vor dem Betrag“.
Wenn der Endpunkt STOREFRONT_UNAVAILABLE liefert, ist die öffentliche API erreichbar, aber für Host und Konfiguration wurde keine aktive Storefront gefunden. Prüfen Sie dann zuerst Site-Aktivierung, verifizierte Tenant Domain, Site-Domain-Bindung und web readiness mit Storefront- und Site-Domains einrichten. Prüfen Sie danach Sales Channel, Katalog, Tenant Init, Preisliste, Währung, Produktveröffentlichung und Produktinhalt. Wechseln Sie nicht auf interne Admin- oder PIM-Endpunkte aus, um die öffentliche Storefront zu umgehen.
Produkte suchen
Suchen Sie Produkte über den Public Catalog:
GET /api/v1/public/v1/catalog/search?q=<query>&limit=24&offset=0Beispielantwort:
{
"productVariantIds": ["uuid"],
"totalCount": 1,
"pagination": {
"limit": 24,
"offset": 0,
"hasMore": false
},
"facets": {
"categories": [
{ "uuid": "uuid", "slug": "schuhe", "name": "Schuhe", "count": 12 }
]
}
}Behandeln Sie productVariantIds als Trefferliste. Die Suche liefert keine vollständige Produktkarte, keine Preise und keine Verfügbarkeit.
Die Suchbasis kann redaktionell gepflegte Synonyme, Korrekturen und kuratierte Ziele enthalten. Diese Regeln werden vorab gepflegt oder aus geprüften KI-Vorschlägen übernommen; der Public-Catalog-Request selbst ruft kein KI-Modell auf. Administratoren und Redakteure pflegen den Ablauf unter Onlineshop-Suche kuratieren.
Kategorien-Facetten einordnen
Nutzen Sie facets.categories, wenn Sie Besucher innerhalb der aktuellen Suche orientieren möchten. Die Kategorie-Facetten gehören zur aktuellen Public-Catalog-Suchbasis: Host, Site, Sales Channel, Locale, Veröffentlichung, Readiness, Suchbegriff und weitere serverseitige Gates grenzen sie ein. Sie werden nicht aus der aktuellen Seite der Trefferliste berechnet.
Beispiel: Eine Suche findet 120 Varianten, limit=24&offset=0 liefert aber nur die ersten 24 Treffer. facets.categories zählt dann die Kategorien der 120 gefundenen Varianten, nicht nur die Kategorien der 24 sichtbaren Treffer. totalCount und Kategorien-Facetten beschreiben also dieselbe gefilterte Suchbasis vor der Pagination.
Wenn Sie die Suche ohne Suchbegriff laden oder q=* senden, nutzt der Server den aktuell öffentlichen Katalog des Storefront-Kontexts als Suchbasis. Die Kategorien-Facetten zeigen dann die Kategorien dieses öffentlichen Katalogs, nicht nur die Kategorien der ersten Trefferseite.
Behandeln Sie diese Facetten als Suchverfeinerung oder flache Navigationshilfe, nicht als vollständigen Kategoriebaum. Eine dauerhafte Kategorienavigation mit Hierarchie, Sortierung, ausgeblendeten leeren Kategorien oder redaktionellen Landingpages braucht einen eigenen öffentlichen Navigations- oder Kategoriebaum-Vertrag. Fragen Sie dafür keine internen Kategorie-, PIM- oder Admin-Endpunkte aus der Storefront ab.
Ein Klick auf eine Kategorie oder Marke darf nur Parameter verwenden, die der Public Catalog Search öffentlich dokumentiert. Nutzen Sie slug-basierte Filter:
GET /api/v1/public/v1/catalog/search?q=*&category=rack-usv&limit=24
GET /api/v1/public/v1/catalog/search?q=*&brand=errepi&limit=24
GET /api/v1/public/v1/catalog/search?q=USV&category=rack-usv&brand=errepi&limit=24category und brand filtern dieselbe öffentliche Suchbasis wie q und attribute[<key>]. Mehrere Werte derselben Dimension wirken als OR, verschiedene Dimensionen wirken als AND. Die Filter laufen vor totalCount, Pagination, Kategorie-Facets, Attribut-Facets, Product Summary und Pricing. Verwenden Sie keine erfundenen Varianten wie categoryId, categorySlug, brandId oder collectionId. collection gehört derzeit nicht zum Public-Catalog-Filtervertrag und wird als ungültige Query abgelehnt.
Die Search-API liefert weiterhin keinen vollständigen Kategoriebaum, keine Markenliste und keine Kollektion-Navigation. Dafür braucht die Storefront einen eigenen öffentlichen Navigationsvertrag oder statisch kuratierte Links.
Attribut-Facetten und Attributfilter nutzen
Fordern Sie öffentliche Attribut-Facetten an, wenn Sie Besucher über echte Produktmerkmale führen möchten:
GET /api/v1/public/v1/catalog/search?q=USV&limit=24&facet=attributesFiltern Sie mit attribute[<key>]:
GET /api/v1/public/v1/catalog/search?q=USV&attribute[rack_or_tower]=rack&limit=24
GET /api/v1/public/v1/catalog/search?q=USV&attribute[rack_units]=2&attribute[service_bypass_option]=trueBeispiel für Attribut-Facetten:
{
"facets": {
"attributes": {
"rack_or_tower": [
{ "value": "rack", "label": "19 Zoll / Rack", "count": 6 },
{ "value": "tower", "label": "Standgerät", "count": 6 }
],
"service_bypass_option": [
{ "value": "true", "label": "true", "count": 4 }
]
}
}
}Der Server wertet nur Attribute aus, die im PIM für öffentliche Filter freigegeben sind. Dafür muss der Attribute-Set-Link öffentlich filterbar sein und die Property darf nicht sensitiv sein. Unbekannte, interne oder sensitive Attribute lehnt die API mit 400 ab. Leiten Sie Produktmerkmale deshalb nicht aus SKU, Namen oder Freitext ab.
Öffentlich filterbar bedeutet nicht automatisch öffentlich sichtbar in Produktantworten. Nutzen Sie Attribut-Facetten nur für Suche und Filter. Wenn Sie Produktmerkmale in Kacheln, Tabellen oder Detailseiten anzeigen möchten, laden Sie publicAttributes über Product Summary oder Product Detail.
facets.attributes zählt dieselbe Suchbasis wie productVariantIds und totalCount, vor limit und offset. Mehrere Werte desselben Attributs wirken als OR. Mehrere verschiedene Attribute wirken als AND. Labels stammen aus Attribut-Wertelisten, wenn eine passende Werteliste gepflegt ist; sonst zeigt die API den öffentlichen Wert als Label.
Attribut-Facetten liefern keine Preise, Bestände oder Verfügbarkeiten. Laden Sie diese Daten weiterhin über die Preis- und Availability-Verträge der Storefront.
Produktkarten laden
Laden Sie Product Summary für die Treffer:
POST /api/v1/public/v1/catalog/products/summary
Content-Type: application/json
{
"variantIds": ["uuid"]
}Beispielantwort:
{
"items": [
{
"variantId": "uuid",
"sku": "SKU-1",
"name": "Produktname",
"shortDescription": "Kurzbeschreibung",
"description": "Beschreibung",
"locale": "de-DE",
"primaryImageUrl": "/assets/tenant/product-assets/asset/version/produkt_1024x0.jpeg",
"unitOfMeasure": "PC",
"categoryIds": ["uuid"],
"categorySlugs": ["schuhe"],
"route": {
"slug": "produktname",
"path": "/de/produkte/produktname"
},
"publicAttributes": [
{
"key": "flavor",
"value": "chicken",
"label": "Geschmack",
"dataType": "string",
"format": "",
"isArray": false,
"unitCode": "",
"sortOrder": 20,
"valueLabels": {
"chicken": "Huhn"
}
}
],
"media": {
"hero": []
}
}
]
}Ordnen Sie Summary-Items der Reihenfolge aus productVariantIds zu. Product Summary liefert öffentliche Identität, Texte, Einheit, Kategorien, öffentliche Route, primaryImageUrl als öffentlich auslieferbaren Alias auf den Medienzweck primary und freigegebene Produktmedien, soweit diese im aktuellen Site-/Sales-Channel-Kontext vorhanden sind. Bild-Aliasse zeigen auf public-safe Renditions, nicht auf originale ProductAsset-Dateinamen. media.<purposeKey>[] bleibt die purpose-basierte Medienprojektion; verwenden Sie primaryImageUrl für einfache Produktkarten, wenn kein spezifischer Media-Purpose benötigt wird. Erwarten Sie aus dieser Antwort keine Preise, Verfügbarkeit, priceState, Bestandsmengen, Preislisten-IDs oder kundenspezifischen Konditionen. Bauen Sie keine Produktkarten aus internen PIM-Antworten.
Verwenden Sie route.path unverändert als Link zur Produktdetailseite. Der Wert enthält bereits den veröffentlichten Site-Präfix und die ausgehandelte Sprache. Ersetzen oder ergänzen Sie keine Sprachsegmente. Fehlt route.path, existiert für diese Produktprojektion keine eindeutige veröffentlichte Detailroute; raten Sie dann keinen Pfad aus route.slug, SKU oder Name.
Product Summary liefert publicAttributes nur, wenn Sie include=publicAttributes setzen:
POST /api/v1/public/v1/catalog/products/summary?include=publicAttributesDer Server liefert nur generische Attribute, deren Attribute-Set-Link isPublicVisible=true trägt und deren Property nicht sensitiv ist. Werte stammen aus Familienattributen und Variantenattributen; Variantenwerte überschreiben Familienwerte. Objekte, verschachtelte Arrays und interne Admin-/Workflow-Metadaten erscheinen nicht.
Für Produktkarten gilt: Verwenden Sie primaryImageUrl als einfaches Produktbild oder lesen Sie direkt media.primary[0], wenn Sie das vollständige Picture-/Rendition-Modell brauchen. Nutzen Sie andere media.<purposeKey>[]-Gruppen nur, wenn die Darstellung einen konkreten Medienzweck wie Hero, Galerie, Datenblatt, Tabelle oder CAD-Download braucht. Wenn ein gelistetes Produkt kein primaryImageUrl liefert, ist das ein Daten- oder Projektionsbefund für die Produktveröffentlichung; bauen Sie keinen Storefront-Fallback aus internen PIM- oder Asset-Antworten.
Wenn die Variante als Abo-Produkt gepflegt ist, kann Product Summary ein subscription-Objekt enthalten:
{
"variantId": "uuid",
"sku": "SUB-1",
"name": "Service-Abo",
"subscription": {
"billingInterval": "month",
"billingIntervalCount": 1,
"minContractDuration": 12,
"noticePeriodDays": 30,
"autoRenew": true
}
}Nutzen Sie diese Werte nur zur Anzeige. Die Storefront darf Intervall, Mindestlaufzeit, Kündigungsfrist oder Verlängerungslogik nicht beim Add-to-Cart setzen oder überschreiben; der Server übernimmt die Konditionen aus dem produktgepflegten ProductService.
Produktdetail laden
Trennen Sie Browser-URL und Produktidentität. Die öffentliche Produktseite verwendet eine Produkt-Route im aktuellen Site-Kontext, zum Beispiel /produkte/t-shirt-basic. Der Site Manager liefert das Detailseiten-Template und den URL-Präfix. ProductRoute.slug identifiziert das Produkt im aktuellen SalesChannel. Bauen Sie kanonische Detail-URLs nicht aus der SKU.
Die kaufbare Identität bleibt die variantId. Laden Sie Details über den öffentlichen Detailpfad:
GET /api/v1/public/v1/catalog/products/{variantId}Nutzen Sie Detaildaten nur für öffentlich freigegebene Produktinformationen. Preise und Verfügbarkeit bleiben getrennte Pricing-Antworten. Product Detail liefert publicAttributes ohne zusätzliches Include, wenn für die Variante öffentliche sichtbare Attribute gepflegt sind.
Wenn die Detailseite über einen Route-Slug geöffnet wird, lösen Sie den Slug im aktuellen Sales-Channel- und Locale-Kontext serverseitig auf und verwenden Sie die zurückgegebene variantId für Produktdetail, Pricing und Warenkorb. Eine ProductRoute macht die Variante unter einem Browserpfad adressierbar. Ob die Variante öffentlich projiziert und kaufbar ist, entscheiden Publication, Public Catalog, Pricing und Checkout als getrennte öffentliche Verträge.
GET /api/v1/public/v1/catalog/products/by-route/produkte/t-shirt-basicDieser Pfad akzeptiert den sichtbaren Detailpfad. Der Server löst daraus den ProductRoute-Slug im aktuellen Site-, SalesChannel- und Locale-Kontext auf. Die Antwort enthält die öffentliche Produktdetailantwort, die aktive variantId, den kanonischen veröffentlichten route.path und auf Wunsch Snippet-Referenzen. Der Response-Pfad wird nicht aus dem angefragten URL-Text übernommen.
Wenn die serverseitige Site-Auslieferung ein Required-Snippet für diese Detailseite einbettet und das Artefakt fehlt, liefert die HTML-Detailseite 503 Service Unavailable mit Retry-After. Dieser Status ist ein Betriebssignal, kein Grund für einen internen API-Fallback. Workspace meldet den Fall als kritischen Telemetry-Fehler server.storefront.product_detail.snippet_missing.
Varianten auf der Detailseite auswählen
Zeigen Sie Variantenauswahlen nur aus variantSelection der öffentlichen Detailantwort an. Typische Achsen sind Größe, Farbe, Packung oder Ausführung. Der Server bildet jede gültige Kombination auf genau eine öffentliche variantId ab. Er nimmt nur Varianten derselben Familie auf, die im aktuellen Tenant, Sales Channel und in der aktuellen Locale öffentlich gelistet sind. Sensitive, nicht öffentliche, unvollständige oder doppelte Kombinationen erscheinen nicht als Auswahlmodell.
Kann der Server keine eindeutige public-safe Variantenmatrix bilden, fehlt variantSelection vollständig. Rendern Sie dann die aktuell geladene Produktvariante ohne Auswahl. Leiten Sie keine Geschwistervarianten, Achsenwerte oder Varianten-IDs aus SKU, Familien-ID, Freitextattributen oder Snippet-Inhalten ab.
Eine Veröffentlichung benötigt keine ProductRoute. Die Route ist nur dann erforderlich, wenn eine Variante eine eigenständige, teilbare Browser-URL erhalten soll. Deshalb kann jede Kombination optional routeSlug enthalten. Die SKU bleibt Anzeige- und Supportinformation; sie ist nie ein URL-Ersatz. Erfinden Sie für eine Kombination ohne routeSlug weder route.path noch einen Slug. Verwenden Sie für das aktuell geladene Produkt den kanonischen route.path der Detailantwort; für eine andere geroutete Kombination lösen Sie deren echten routeSlug über den öffentlichen By-Route-Vertrag auf.
Beispielantwort:
{
"variantId": "11111111-1111-1111-1111-111111111111",
"unitOfMeasure": "PC",
"variantSelection": {
"axes": [
{
"key": "size",
"label": "Größe",
"dataType": "string",
"sortOrder": 10,
"values": [
{ "value": "M", "label": "M" },
{ "value": "L", "label": "L" }
]
},
{
"key": "color",
"label": "Farbe",
"dataType": "string",
"sortOrder": 20,
"values": [{ "value": "red", "label": "Rot" }]
}
],
"combinations": [
{
"variantId": "11111111-1111-1111-1111-111111111111",
"values": { "size": "M", "color": "red" },
"routeSlug": "t-shirt-m-rot"
},
{
"variantId": "22222222-2222-2222-2222-222222222222",
"values": { "size": "L", "color": "red" }
}
]
}
}Sie können die Bedienung passend zur Storefront wählen:
| Modus | URL-Verhalten | Umsetzung |
|---|---|---|
| Normale Navigation | Wechselt auf die Route der Zielvariante. | Rendern Sie einen normalen Link. Dieser Modus funktioniert ohne JavaScript und lädt die Detailseite vollständig neu. |
| Progressive Fragmentumschaltung | Aktualisiert die URL auf die echte Zielroute, tauscht aber nur vorgerenderte Produkt-Snippets. | Nutzen Sie den Site-Manager-Vertrag aus Snippet-Vorlagen entwickeln. Bei Fehlern navigiert der Browser zur Zielroute. |
| Route-loser Wechsel | Lässt die URL unverändert. | Nutzen Sie ein JavaScript-Control mit konkreter variantId. Dieser Zustand ist nicht als eigene URL teilbar. |
Wollen Sie jede Variante öffentlich adressieren, pflegen Sie für jede Variante eine live geschaltete ProductRoute. Wollen Sie eine gemeinsame Detail-URL mit route-losen Größen oder Farben, veröffentlichen Sie nur die benötigten Routen und behandeln die übrigen Kombinationen als nicht adressierbare Interaktionszustände.
Bauen Sie die Auswahl so auf:
- Lösen Sie die sichtbare Produkt-Route über
products/by-routeauf. - Speichern Sie die zurückgegebene
variantIdals aktive Variante. - Rendern Sie Achsen, Labels und auswählbare Werte ausschließlich aus
variantSelection. - Aktualisieren Sie bei jeder Auswahl die aktive
variantIdaus diesem öffentlichen Auswahlmodell. Erzeugen Sie keinevariantIdim Client. - Laden Sie Detaildaten und variantenspezifische Snippets für die gewählte
variantIdneu oder navigieren Sie vollständig zur Zielroute. - Laden Sie Preis und Verfügbarkeit über
pricing/resolvefür die aktivevariantId, Menge und öffentliche Einheit. - Senden Sie beim Add-to-Cart nur die aktive
variantId,quantity,unitOfMeasureund optional zulässigeconfiguration.
| Do | Don’t |
|---|---|
Nutzen Sie sichtbare Labels wie Größe oder Farbe für Controls und stabile Keys nur intern. | Bauen Sie keine Achsen aus Freitext, Snippets oder CSS-Klassen. |
| Deaktivieren Sie Kombinationen, für die die öffentliche Antwort keine passende Variante liefert. | Erlauben Sie keine Kombination, für die Sie keine konkrete variantId haben. |
| Laden Sie Pricing und Availability nach jeder Variantenwahl neu. | Übernehmen Sie Preis oder Bestand aus der vorherigen Variante. |
Aktualisieren Sie die URL nur mit einer echten routeSlug; lassen Sie sie bei route-losen Varianten unverändert. | Verwenden Sie SKU, Attributwerte oder Preislisten als kanonische URL. |
| Zeigen Sie SKU als Supportinformation an, wenn sie öffentlich zurückkommt. | Senden Sie SKU statt variantId an Pricing oder Warenkorb. |
Beispiel: Die Detailseite /produkte/t-shirt-m-rot startet mit T-Shirt M Rot. Wählt der Besucher L und die Kombination besitzt routeSlug=t-shirt-l-rot, kann der Shop vollständig dorthin navigieren oder die Snippets progressiv tauschen und dieselbe URL per History API setzen. Besitzt L Rot keine Route, kann der Shop den Zustand per JavaScript aktivieren und Preis sowie Verfügbarkeit neu laden; die URL bleibt dann /produkte/t-shirt-m-rot. In beiden Fällen erhält der Warenkorb ausschließlich die neue variantId.
Fehlende Variantenkombinationen behandeln
Behandeln Sie Achsenwerte nicht als unabhängige Listen. Die gültige Auswahl ist immer die konkrete Kombination, für die die öffentliche Antwort eine Produktvariante liefert.
Beispielmatrix:
| Größe / Farbe | Rot | Grün | Blau |
|---|---|---|---|
| S | Variante vorhanden | Variante vorhanden | Variante vorhanden |
| M | Variante vorhanden | keine Variante | Variante vorhanden |
| L | Variante vorhanden | Variante vorhanden | keine Variante |
Für diese Matrix gilt:
- Wenn der Besucher
Mwählt, darfGrünnicht als kaufbare Kombination auswählbar bleiben. - Wenn der Besucher
Blauwählt, darfLnicht als kaufbare Kombination auswählbar bleiben. - Wenn der Besucher zuerst
Grünwählt, darfMnicht als kaufbare Kombination auswählbar bleiben. - Wenn bereits
MundRotaktiv sind und der BesucherGrünwählt, muss der Client entweder auf eine gültige grüne Größe wechseln oder die Auswahl als unvollständig markieren. Er darf nicht mit der altenvariantIdweiter kaufen.
Nutzen Sie dafür ein Kombinationsmodell:
- Lesen Sie alle öffentlich gelieferten Variantenkombinationen für die Produktseite.
- Bilden Sie daraus eine Zuordnung von Achsenwerten zu
variantId, zum Beispielsize=M;color=blue -> variantId. - Prüfen Sie bei jeder Änderung, welche Kombinationen mit der aktuellen Teilauswahl noch möglich sind.
- Markieren Sie unmögliche Werte sichtbar als deaktiviert oder nicht verfügbar.
- Setzen Sie die aktive
variantIdnur, wenn genau eine vollständige gültige Kombination gewählt ist. - Laden Sie Pricing und Availability für diese
variantId. Erst danach aktivieren Sie Add-to-Cart.
Unterscheiden Sie zwei Fälle:
| Fall | UI-Verhalten | Warenkorb |
|---|---|---|
Kombination existiert nicht, zum Beispiel L + Blau | Wert deaktivieren oder als nicht kombinierbar anzeigen. | Kein Add-to-Cart, weil keine variantId existiert. |
| Kombination existiert, ist aber nicht verfügbar | Variante auswählbar lassen und Verfügbarkeitsstatus anzeigen. | Add-to-Cart nur erlauben, wenn Pricing/Availability es erlaubt. |
Speichern Sie im Client also nicht nur selectedSize und selectedColor, sondern immer auch den daraus aufgelösten Zustand: keine vollständige Auswahl, ungültige Kombination, aktive variantId mit Verfügbarkeitsstatus oder aktive variantId mit kaufbarer Verfügbarkeit.
Wenn Varianten fachlich eigene Produktseiten brauchen, zum Beispiel bei eigenständigen Modellen oder stark unterschiedlichen Inhalten, pflegen Sie eigene Produkt-Routen. Verwenden Sie auch dann Route-Slugs statt SKUs als Browser-URL.
Produktrelationen rendern
Der Produktdetail-Endpunkt kann gruppierte Empfehlungen als recommendationGroups liefern. Rendern Sie diese Gruppen nur aus der öffentlichen Detailantwort, zum Beispiel Zubehör, Optionen, größere Alternativen oder Ersatzteile. Laden Sie die Zielvarianten nicht über interne PIM-, Admin- oder Inventory-Endpunkte nach.
Die Gruppen verwenden technische type-Keys wie accessory, cross_sell, up_sell, substitute, spare_part und bonus_item. Nutzen Sie diese Keys für Logik, Sortierung und Tracking. Übersetzen Sie nur sichtbare Überschriften wie „Zubehör“, „Alternativen“ oder „Ersatzteile“ in der Storefront.
Die Items in recommendationGroups sind bereits auf den aktuellen Storefront-Kontext gefiltert. Wenn eine erwartete Relation fehlt, prüfen Sie Publication, is_listed, Sales Channel, Locale, Lifecycle, Content und Readiness der Zielvariante. Eine gepflegte Relation allein macht ein Zielprodukt nicht öffentlich sichtbar.
Lösen Sie Preise und Verfügbarkeit für angezeigte Zielvarianten separat über Pricing auf. Übernehmen Sie keine Preiswerte aus Snippets oder internen Preislisten.
Preise und Verfügbarkeit auflösen
Fragen Sie Pricing für konkrete Mengen an:
POST /api/v1/public/v1/pricing/resolve
Content-Type: application/json
{
"items": [
{
"variantId": "uuid",
"quantity": "1",
"unitOfMeasure": "PC"
}
]
}Für ein verkaufbares Mischgebinde senden Sie zusätzlich pricingTargetType: "assortment_pack". Die variantId bleibt die Trägervariante des Mischgebindes. Verwenden Sie diesen Target-Typ nur, wenn das PIM-Mischgebinde Verkaufbar ist und für die relevante Preisliste ein Mischgebindepreis existiert.
{
"items": [
{
"variantId": "uuid-der-traegervariante",
"pricingTargetType": "assortment_pack",
"quantity": "1",
"unitOfMeasure": "PC"
}
]
}Die Antwort enthält pricingTargetType sowie Code- und Namenssnapshots des Mischgebindes. Rendern Sie den Preis als Preis des Mischgebindes und berechnen Sie ihn nicht lokal aus den enthaltenen Varianten. Für Verfügbarkeit prüft der Server die physischen Kindvarianten und deren Mengen.
Preisentscheidung und Gültigkeit
Die Preisentscheidung läuft serverseitig im Storefront-Kontext. Senden Sie für normale Storefront-Requests keinen eigenen Gültigkeitszeitpunkt. Workspace verwendet den aktuellen UTC-Zeitpunkt des Servers und speichert Zeitwerte an der API-Grenze als UTC. Wenn ein Admin- oder Import-Client Zeitwerte mit Offset schreibt, normalisiert die API diese Werte vor dem Speichern nach UTC.
Die Gültigkeitsgrenzen eines Produktpreises sind inklusiv. Ein Preis gilt also ab dem exakten validFrom-Zeitpunkt und bis einschließlich zum exakten validUntil-Zeitpunkt. Fehlt für die angefragte Variante, Preisliste, Zielart, Einheit und Menge ein gültiger Produktpreis, behandelt der Commerce-Service den Fall als ErrPriceNotFound; öffentliche Storefronts zeigen dann keinen selbst berechneten Ersatzpreis.
Beachten Sie diese Entscheidungsregeln:
| Fall | Serververhalten | Client-Verhalten |
|---|---|---|
validFrom entspricht exakt dem UTC-Zeitpunkt der Preisfindung. | Der Produktpreis ist gültig. | Preis aus der Antwort anzeigen. |
validUntil entspricht exakt dem UTC-Zeitpunkt der Preisfindung. | Der Produktpreis ist noch gültig. | Preis aus der Antwort anzeigen. |
| Kein Produktpreis ist im UTC-Fenster gültig. | Die interne Preisfindung liefert ErrPriceNotFound; öffentliche Antworten enthalten keinen Preisbetrag für diese Position. | Keinen Preis erraten, keine letzte bekannte Staffel anzeigen, Anfrage- oder Login-/Hinweiszustand rendern. |
| Mehrere gültige Staffeln passen zur Menge. | Die höchste passende minQuantity gewinnt vor dem Validitäts-Tie-Break. | Bei Mengenänderung Pricing erneut laden. |
| Staffeln sind abgelaufen oder beginnen erst später. | Public-Storefront-Preisstaffeln filtern diese Einträge aus. | Nur gelieferte Staffeln anzeigen. |
| PIM-Projektionen suchen einen Primärpreis. | Soft-deleted Preise, falsche Zielarten und zeitlich inaktive Preise zählen nicht. | Produktkarten nicht aus internen Preislisten rekonstruieren. |
Beispielantwort für sichtbare Preise:
{
"items": [
{
"variantId": "uuid",
"quantity": "1.0000",
"unitOfMeasure": "PC",
"exposure": "visible",
"priceState": "visible",
"availabilityState": "available",
"displayMode": "gross",
"unitNet": { "amount": "10.00", "currency": "EUR" },
"unitGross": { "amount": "11.90", "currency": "EUR" },
"lineNet": { "amount": "10.00", "currency": "EUR" },
"lineGross": { "amount": "11.90", "currency": "EUR" },
"basePrice": {
"quantity": "100",
"unitOfMeasure": "G",
"net": { "amount": "1.00", "currency": "EUR" },
"gross": { "amount": "1.19", "currency": "EUR" }
},
"presentation": {
"tileGross": { "amount": "10.71", "currency": "EUR" },
"basePrice": {
"quantity": "100",
"unitOfMeasure": "G",
"net": { "amount": "1.00", "currency": "EUR" },
"gross": { "amount": "1.19", "currency": "EUR" }
},
"tileQualifier": "from",
"quantityBasis": "10.0000",
"unitOfMeasure": "PC",
"hasQuantityTiers": true,
"requiresRepriceForQuantity": true
}
}
]
}Bei Abo-Produkten enthält die Pricing-Antwort dieselben produktgetriebenen Konditionen zusätzlich am Pricing-Item:
{
"items": [
{
"variantId": "uuid",
"priceState": "visible",
"availabilityState": "available",
"unitGross": { "amount": "29.90", "currency": "EUR" },
"subscription": {
"billingInterval": "month",
"billingIntervalCount": 1,
"minContractDuration": 12,
"noticePeriodDays": 30,
"autoRenew": true
}
}
]
}Zeigen Sie diese Laufzeitinformationen unmittelbar in Produktkarte, Produktdetail und Checkout-Zusammenfassung an, wenn sie für die Kaufentscheidung relevant sind. Verwenden Sie für den Warenkorb weiterhin nur variantId, quantity, unitOfMeasure und zulässige configuration.
Beispielantwort für Login-Pflicht:
{
"items": [
{
"variantId": "uuid",
"quantity": "1.0000",
"unitOfMeasure": "PC",
"exposure": "login_required",
"priceState": "login_required",
"availabilityState": "login_required"
}
]
}Client-Verhalten:
- Zeigen Sie nur serverseitig gelieferte Beträge an.
- Rufen Sie Pricing bei Mengenänderungen erneut auf.
- Nutzen Sie
purchaseQuantityRule, um erlaubte Mengen in Steppern oder Selects anzubieten. Behandeln Sie die Regel als UI-Hinweis; der Server entscheidet weiter verbindlich. - Nutzen Sie
presentationfür Kachelpreise, wenn es vorhanden ist. - Nutzen Sie
basePricefür die Grundpreisanzeige der angefragten Menge. - Nutzen Sie
presentation.basePrice, wenn Sie den Kachelpreis auspresentation.tileGrossoderpresentation.tileNetanzeigen. - Zeigen Sie keinen Grundpreis an, wenn
basePricefehlt. Berechnen Sie Grundpreise nicht im Client nach. - Bauen Sie keine Staffelpreistabelle aus internen Preislisten.
- Zeigen Sie bei
priceState=login_requiredeinen Login-Hinweis. - Zeigen Sie bei
priceState=quote_onlyeine Angebotsanfrage. - Verbergen Sie Preise bei
priceState=hidden. - Behandeln Sie
availabilityState=availableals verfügbare angefragte Menge. - Behandeln Sie
availabilityState=unavailableals serverseitigen Bestandsblocker für die angefragte Menge. - Zeigen Sie bei
availabilityState=coarsenur eine grobe Aussage wie „verfügbar“ oder „auf Anfrage“, aber keine Bestandsmenge. - Zeigen Sie bei
availabilityState=hiddenoderavailabilityState=login_requiredkeine exakten Bestände. - Behandeln Sie
availabilityState=review_requiredwie einen Anfrage- oder Review-Pfad.
Dynamische Produktdetailseiten können serverseitig Product-JSON-LD ausgeben. Workspace ergänzt ein Offer nur, wenn Preis und Availability im anonymen öffentlichen Kontext belastbar sind. Wenn Preis, Unit, Rate-Limit oder Availability nicht zuverlässig öffentlich sind, bleibt die Produktseite auslieferbar und das JSON-LD enthält kein Offer.
Grundpreise erscheinen nur, wenn PIM eine wirksame Grundpreisanzeige für die Variante liefert. Varianten erben die Einstellung der Produktfamilie, können sie aber überschreiben oder ausblenden. Wenn eine Pflichtkonfiguration ungültig ist, liefert der öffentliche Pricing-Pfad keine Preisbeträge für diese Position. Behandeln Sie das wie einen nicht anzeigbaren Preiszustand und zeigen Sie keinen berechneten Ersatzwert.
Snippets verwenden
Produkt-Snippets sind vorgerenderte Darstellungsartefakte. Sie können Produktkarten oder Detailbestandteile rendern, sind aber nicht die Quelle für Preise, Verfügbarkeit oder Checkout-Entscheidungen.
Nutzen Sie Snippets nur, wenn sie zum gleichen Tenant-, Variant-, Locale- und Sales-Channel-Kontext passen und Ihnen als eigenes Darstellungsartefakt vorliegen. Die Snippet-Vorlage und der Rebuild-Scope entscheiden, ob ein globales, sprachbezogenes, channelbezogenes oder kombiniertes Artefakt existiert.
Auf Detailseiten eignen sich Snippets für vorbereitete Bereiche wie Hero, Kurzbeschreibung, redaktionelle Produktdetails, technische Daten, Empfehlungskacheln oder wiederverwendbare Content-Blöcke. Binden Sie ein Detail-Snippet für die aktive Variante und den aktuellen Storefront-Scope ein. Wenn der Nutzer eine andere Variante auswählt, prüfen Sie den Snippet-Scope für die neue variantId neu.
Wenn ein Snippet Empfehlungen oder Zubehör rendert, verwendet es recommendation_groups aus dem Snippet-ViewModel. Behandeln Sie diese Daten wie eine Darstellungshilfe für public-safe Produktrelationen. Preise, Verfügbarkeit und Warenkorbzustände laden Sie weiterhin über die öffentlichen Storefront-Verträge.
Behandeln Sie Snippets als Darstellung, nicht als Vertragsquelle für Katalogdaten. Wenn ein Site-Template einen Detailbereich als optionales Snippet pflegt, lassen Sie den Bereich weg oder rendern Sie aus öffentlichen Produktdaten. Wenn das Site-Template ein Snippet als Pflichtbereich markiert, antwortet die Detailseite mit 503, bis der Snippet-Rebuild das Artefakt für Tenant, Variante, Locale und SalesChannel erzeugt hat.
PIM-Inhaltsschemata und Inhaltsprofile steuern, welche strukturierten Produktinhalte pro Zweck, Produktfamilie, Sprache und Vertriebskanal gepflegt werden. Öffentliche Storefronts lesen trotzdem nur die veröffentlichten Public-Catalog- und Pricing-Verträge. Greifen Sie nicht direkt auf interne PIM-Inhalte, Inhaltsschemata oder Snippet-Vorlagen zu. Der fachliche Pflegeablauf ist in Produktinhalte pflegen beschrieben. Die Konfiguration steht in PIM-Inhalte konfigurieren. Snippet-Details stehen in Snippet-Vorlagen entwickeln.
Produkt erscheint nicht
Prüfen Sie bei leeren Listen:
- Prüfen Sie zuerst, ob der Storefront Context ohne
STOREFRONT_UNAVAILABLElädt. - Verwenden Sie
/api/v1/public/v1/catalog/search, nicht die Website-Suche. - Laden Sie Storefront Context und notieren Sie Locale, Währung, Sales Channel, Gastkauf und Zahlarten.
- Prüfen Sie, ob die Suche mit
q=*oder ohneqTreffer liefert. - Behandeln Sie
totalCount=0als gültige Antwort, nicht als Berechtigungs-Bypass. - Laden Sie Product Summary erst nach Search.
- Weichen Sie nicht auf interne Endpunkte aus.
- Übergeben Sie an das Admin-Team Host, Search-URL, Status,
totalCount, Query, Context,web readiness-Blocker und erwartete SKUs oder Variant-IDs.
Das Admin-Team prüft anschließend zuerst den Domainfluss über Storefront- und Site-Domains einrichten: aktive Site, verifizierte Tenant Domain, Site-Domain-Bindung und web readiness. Danach prüft es Sales Channel, Product Publication, is_listed, is_searchable, Lifecycle, Product Content, Locale, Readiness und Snippet-Rebuild.