> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Individuelle Integration

> Rufe die Strategies API direkt auf, um individuelle Storefront-Erlebnisse zu ermöglichen — PDP-Karussells, individuelle Warenkorb-Upsells und überall dort, wo die nativen Oberflächen von Aftersell nicht hinreichen.

<div id="overview">
  ## Überblick
</div>

Wenn weder die nativen Oberflächen von Aftersell (Post-Purchase, Checkout, Upcart) noch eine fertige Integration passen, kannst du die Strategies API selbst aus deinem Shopify-Theme aufrufen und die zurückgegebenen Produkte beliebig rendern.

Das Muster ist in jedem Fall dasselbe: Baue einen Kontext-Payload aus Liquid (damit Shopify-Attribute wie das aktuelle Produkt, der Warenkorbinhalt und Kundenfelder zur Renderzeit befüllt werden), sende ihn per `POST` an `/api/public/strategy/evaluate` und rendere die Antwort.

Diese Seite behandelt zwei Implementierungsmuster:

* **PDP-Kontext** — platziere eine Section auf Produktseiten, die die API mit dem **aktuell angezeigten Produkt** aufruft und ein Karussell mit den zurückgegebenen Empfehlungen rendert.
* **Warenkorb-Kontext** — rendere einen Upsell-Block in einem individuellen Warenkorb, der die API mit **allen aktuellen Warenkorbpositionen** aufruft und die zurückgegebenen Produkte rendert.

Der Unterschied zwischen beiden liegt in der Form des **Produktkontexts**: ein einzelnes Produkt auf der PDP, ein Array aller Positionen im Warenkorb.

***

<div id="what-youll-need">
  ## Was du brauchst
</div>

1. **Deinen Strategy-API-Key.** Gehe in Aftersell zu **Settings → Product Strategy** und kopiere im Bereich **Security Token** deinen Token (das ist dein Strategy-API-Key).
2. **Die Strategy-ID.** Öffne die Strategy, die du ausführen möchtest, im Aftersell-Strategy-Editor und kopiere ihre ID.
3. **Zugriff auf den Theme-Code.** Du fügst deinem Shopify-Theme eine Liquid-Section (PDP) oder einen Block (individueller Warenkorb) hinzu — Online Store → Themes → ... → Edit code.

<Warning>
  Dein Strategy-API-Key liegt im clientseitigen Theme-Code und ist damit für jeden sichtbar, der den Quelltext der Seite ansieht. Behandle ihn als öffentliches Storefront-Credential und rotiere ihn über **Settings → Product Strategy** in Aftersell, falls er jemals auf unbeabsichtigte Weise offengelegt wird.
</Warning>

***

<div id="pdp-context-section-snippet">
  ## PDP-Kontext: Section-Snippet
</div>

Dieses Muster fügt deiner Produktseite eine Shopify-Section hinzu. Beim Rendern der Seite bettet Liquid das aktuelle Produkt, den Warenkorb und die Kundenattribute in den Payload ein, dann sendet JavaScript einen POST an die Strategies API und rendert die zurückgegebenen Produkte in einem Splide-Karussell.

<div id="installing">
  ### Installation
</div>

1. Gehe in deinem Shopify-Adminbereich zu **Online Store → Themes**, klicke bei deinem Theme auf **...** und wähle **Edit code**.
2. Erstelle im Ordner **Sections** eine neue Datei mit dem Namen `aftersell-upsell-carousel.liquid`.
3. Füge das untenstehende Snippet in die neue Datei ein und ersetze `YOUR_STRATEGY_API_KEY` durch den API-Key aus Aftersell.
4. Speichere.
5. Öffne dein Produkt-Template (üblicherweise `templates/product.json` oder `sections/main-product.liquid`) und füge die Section **Aftersell Carousel** dort ein, wo das Karussell erscheinen soll. Im Theme-Editor kannst du sie auch direkt auf die Produktseite ziehen.
6. Füge in den Einstellungen der Section deine **Strategy-ID** ein.

<div id="what-the-section-sends">
  ### Was die Section sendet
</div>

Für jeden PDP-Aufruf enthält der Payload:

* **products** — ein Array mit einem einzigen Element, das das aktuell angezeigte Produkt enthält (productId, variantId, quantity, price, handle, title, vendor, productType, tags, collections, sellingPlan).
* **cart** — Zwischensumme, Artikelanzahl und Positionsanzahl des aktuellen Warenkorbs des Käufers (entfällt bei leerem Warenkorb).
* **cartToken** — damit die API diese Auswertung derselben Session zuordnen kann.
* **customer** — Tags, Land, Provinz, Locale, Bestellanzahl, Gesamtausgaben und Accepts-Marketing-Flag, aber **nur wenn der Käufer eingeloggt ist**.
* **session** — Währungscode aus `shop.currency`.

Die Section sendet standardmäßig keine UTM-Parameter. Wenn du UTM-basiertes Targeting auf der PDP möchtest, erfasse sie clientseitig und füge sie vor dem Fetch dem `session`-Objekt hinzu.

<div id="the-snippet">
  ### Das Snippet
</div>

```liquid theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
{% comment %}
  Aftersell Carousel (Splide)
  Type: Section — save to sections/aftersell-upsell-carousel.liquid
{% endcomment %}

{% if product %}

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide-core.min.css">
<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js" defer></script>

<div id="aftersell-upsell-{{ section.id }}" class="aftersell-upsell-carousel" style="display:none" {{ section.shopify_attributes }}>
  <h2 class="aftersell-heading">{{ section.settings.heading | default: 'You might also like' }}</h2>
  <div class="aftersell-carousel-wrapper">
    <button class="aftersell-arrow aftersell-arrow--prev" aria-label="Previous" disabled>&#8592;</button>
    <div class="aftersell-track-container">
      <div class="splide" id="aftersell-splide-{{ section.id }}">
        <div class="splide__track">
          <ul class="splide__list">
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
          </ul>
        </div>
      </div>
    </div>
    <button class="aftersell-arrow aftersell-arrow--next" aria-label="Next" disabled>&#8594;</button>
  </div>
</div>

<style>
.aftersell-upsell-carousel { font-family: Modernist, sans-serif; max-width: 1300px; margin: 0 auto; padding: 24px 0 0; box-sizing: border-box; }
.aftersell-heading { font-family: Modernist, sans-serif; font-size: 30px; font-weight: 700; line-height: 45px; color: #0C0A09; margin: 0; }
.aftersell-carousel-wrapper { display: flex; align-items: center; gap: 8px; }
.aftersell-track-container { overflow: hidden; flex: 1; min-width: 0; }
.aftersell-card { display: flex; flex-direction: column; box-sizing: border-box; height: 100%; }
.aftersell-card-link { text-decoration: none; color: inherit; display: block; flex: 1; }
.aftersell-card-image { aspect-ratio: 1; overflow: hidden; background: #f5f5f5; border-radius: 8px 8px 0 0; position: relative; }
.aftersell-card-image img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.3s ease; }
.aftersell-card-image:hover img { transform: scale(1.04); }
.aftersell-card-badge { position: absolute; top: 10px; left: 8px; background: #c60006; color: #fff; font-size: 11px; font-weight: 500; padding: 4px 8px; border-radius: 16px; z-index: 1; }
.aftersell-card-body { padding: 6px 0 0; display: flex; flex-direction: column; }
.aftersell-card-vendor { font-size: 13px; font-weight: 550; text-transform: uppercase; color: #1d4481; margin: 0; }
.aftersell-card-title { font-size: 13px; font-weight: 700; color: #0C0A09; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aftersell-card-price { font-size: 13px; color: #0C0A09; margin: 0; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.aftersell-card-price--compare { text-decoration: line-through; color: #595959; }
.aftersell-card-price--sale { font-weight: 700; color: #c60006; }
.aftersell-card-actions { padding: 6px 0 0; display: flex; flex-direction: column; margin-top: auto; }
.aftersell-variant-select { width: 100%; font-size: 13px; padding: 0.35rem 0.5rem; border: 1px solid #DBDBDB; border-radius: 4px; background: #fff; color: #0C0A09; cursor: pointer; }
.aftersell-cta { padding: 16px 12px; background: #c50007; color: #FFFFFF; border: none; border-radius: 5px; font-size: 16px; font-weight: 700; cursor: pointer; width: 100%; display: flex; align-items: center; justify-content: center; transition: background 0.2s, opacity 0.2s; }
.aftersell-cta:hover:not(:disabled) { opacity: 0.85; }
.aftersell-cta:disabled { opacity: 0.5; cursor: default; }
.aftersell-cta--added { background: #1d4481; }
.aftersell-cta--unavailable { background: #999; cursor: default; }
.aftersell-arrow { width: 30px; height: 90px; background: rgba(255,255,255,0.6); border: 1px solid #DBDBDB; border-radius: 5px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; padding: 0; }
.aftersell-arrow:disabled { opacity: 0.3; cursor: default; }
.aftersell-skeleton { border-radius: 8px; background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: aftersell-shimmer 1.4s infinite; aspect-ratio: 0.75; width: 100%; }
@keyframes aftersell-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

#aftersell-splide-{{ section.id }} .splide__arrows,
#aftersell-splide-{{ section.id }} .splide__pagination { display: none !important; }

@media (max-width: 768px) {
  .aftersell-upsell-carousel { padding: 16px 16px 0; }
  .aftersell-arrow { display: none; }
  .aftersell-carousel-wrapper { gap: 0; }
}
</style>

<script>
(function() {
  var BACKEND_URL  = 'https://start.aftersell.app';
  var API_KEY      = 'YOUR_STRATEGY_API_KEY';
  var STRATEGY_ID  = {{ section.settings.strategy_id | json }};
  var SHOP_DOMAIN  = {{ shop.permanent_domain | json }};
  var CTA_LABEL    = '{{ section.settings.cta_label | default: "Add to cart" }}';
  var MAX_PRODUCTS = {{ section.settings.max_products | default: 8 }};
  var CURRENCY     = {{ shop.currency | default: "USD" | json }};
  var SECTION_ID   = {{ section.id | json }};
  if (!SHOP_DOMAIN || !STRATEGY_ID) return;

  var fmt;
  try {
    fmt = new Intl.NumberFormat('en-US', { style: 'currency', currency: CURRENCY });
  } catch(e) {
    fmt = { format: function(n) { return '$' + parseFloat(n).toFixed(2); } };
  }
  function money(str) { return fmt.format(parseFloat(str) || 0); }

  function gidToNumeric(gid) {
    return gid ? String(gid).split('/').pop() : null;
  }

  function priceHtml(price, compareAt) {
    var hasSale = compareAt && parseFloat(compareAt) > parseFloat(price);
    return hasSale
      ? '<span class="aftersell-card-price--compare">' + money(compareAt) + '</span>'
        + '<span class="aftersell-card-price--sale">' + money(price) + '</span>'
      : '<span class="aftersell-card-price--current">' + money(price) + '</span>';
  }

  var productContext = {
    productId:   'gid://shopify/Product/{{ product.id }}',
    variantId:   'gid://shopify/ProductVariant/{{ product.selected_or_first_available_variant.id }}',
    quantity:    1,
    price:       {{ product.price | divided_by: 100.0 }},
    handle:      {{ product.handle | json }},
    title:       {{ product.title | json }},
    vendor:      {{ product.vendor | json }},
    productType: {{ product.type | json }},
    tags:        {{ product.tags | json }},
    collections: [{% for col in product.collections %}'gid://shopify/Collection/{{ col.id }}'{% unless forloop.last %},{% endunless %}{% endfor %}],
    sellingPlan: {% if product.selected_selling_plan %}'subscription'{% else %}'one-time'{% endif %}
  };

  var cartContext = {
    subtotal:  {{ cart.total_price | divided_by: 100.0 }},
    itemCount: {{ cart.item_count }},
    lineCount: {{ cart.items.size }}
  };

  {% if customer %}
  var customerContext = {
    customerId:       'gid://shopify/Customer/{{ customer.id }}',
    tags:             {{ customer.tags | json }},
    {% if customer.default_address.country_code %}countryCode: {{ customer.default_address.country_code | json }},{% endif %}
    {% if customer.default_address.province_code %}provinceCode: {{ customer.default_address.province_code | json }},{% endif %}
    locale:           {{ request.locale.iso_code | json }},
    orderCount:       {{ customer.orders_count }},
    totalSpent:       {{ customer.total_spent | times: 1.0 }},
    acceptsMarketing: {{ customer.accepts_marketing }}
  };
  {% endif %}

  var blockEl  = document.getElementById('aftersell-upsell-' + SECTION_ID);
  var splideEl = document.getElementById('aftersell-splide-' + SECTION_ID);
  var list     = splideEl.querySelector('.splide__list');
  var prevBtn  = blockEl.querySelector('.aftersell-arrow--prev');
  var nextBtn  = blockEl.querySelector('.aftersell-arrow--next');
  var splideInstance = null;

  function getCartToken() {
    var t = {{ cart.token | json }};
    if (t) return Promise.resolve(t);
    return fetch('/cart.js').then(function(r){ return r.json(); }).then(function(c){ return c.token || null; }).catch(function(){ return null; });
  }

  function evaluate() {
    getCartToken().then(function(cartToken) {
      if (!cartToken) return;
      return fetch(BACKEND_URL + '/api/public/strategy/evaluate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Strategy-Api-Key': API_KEY },
        body: JSON.stringify({
          shopDomain:  SHOP_DOMAIN,
          strategyId:  STRATEGY_ID,
          context: {
            products:  [productContext],
            cartToken: cartToken,
            cart:      cartContext.itemCount > 0 ? cartContext : undefined,
            {% if customer %}customer: customerContext,{% endif %}
            session:   { currencyCode: CURRENCY }
          }
        })
      });
    }).then(function(res) { if (!res) return; return res.json(); })
    .then(function(data) {
      if (!data || !data.success || !data.products || !data.products.length) {
        clearSkeletons(); return;
      }
      renderCards(data.products.slice(0, MAX_PRODUCTS));
    }).catch(function(err) {
      console.error('[AfterSell carousel] Error:', err);
      clearSkeletons();
    });
  }

  function renderCards(products) {
    if (!products || !products.length) {
      blockEl.style.display = 'none'; return;
    }

    blockEl.style.display = '';

    list.innerHTML = products.map(function(p, i) {
      var img      = p.images && p.images[0] ? p.images[0].src : '';
      var alt      = (p.images && p.images[0] && p.images[0].altText) || p.title;
      var variants = p.variants || [];
      var availableVariants = variants.filter(function(v) {
        return v.availableForSale !== false;
      });
      var isDefaultVariant = variants.length === 1 && variants[0].title === 'Default Title';
      var showSelect       = !isDefaultVariant && availableVariants.length >= 1;
      var firstVariant     = availableVariants.length ? availableVariants[0] : null;
      var firstVariantId   = firstVariant ? gidToNumeric(firstVariant.variantId) : null;
      var anyAvailable     = availableVariants.length > 0;

      var onSale = p.compareAtPrice && parseFloat(p.compareAtPrice) > parseFloat(p.price);
      var badge  = '';
      if (onSale) {
        var pct = Math.round((1 - parseFloat(p.price) / parseFloat(p.compareAtPrice)) * 100);
        badge = '<span class="aftersell-card-badge">Save ' + pct + '%</span>';
      }
      var vendor = p.vendor ? '<p class="aftersell-card-vendor">' + p.vendor + '</p>' : '';

      var selectHtml = '';
      if (showSelect) {
        var options = availableVariants.map(function(v) {
          return '<option value="' + gidToNumeric(v.variantId) + '"'
            + ' data-price="'   + (v.price         || p.price) + '"'
            + ' data-compare="' + (v.compareAtPrice || '')     + '"'
            + '>' + v.title + '</option>';
        }).join('');
        selectHtml = '<select class="aftersell-variant-select" aria-label="Select variant">' + options + '</select>';
      }

      var atcLabel = anyAvailable ? CTA_LABEL : 'Sold Out';
      var atcClass = anyAvailable ? 'aftersell-cta' : 'aftersell-cta aftersell-cta--unavailable';
      var atcBtn   = '<button class="' + atcClass + '"'
        + (firstVariantId ? ' data-variant-id="' + firstVariantId + '"' : '')
        + (anyAvailable ? '' : ' disabled')
        + '>' + atcLabel + '</button>';

      var productUrl = p.url + (p.url.indexOf('?') > -1 ? '&' : '?') + 'ref=aftersell';

      return '<li class="splide__slide">'
        + '<div class="aftersell-card" data-idx="' + i + '">'
        + '<a class="aftersell-card-link" href="' + productUrl + '">'
        + '<div class="aftersell-card-image">' + badge
        + (img ? '<img src="' + img + '" alt="' + alt + '" loading="lazy">' : '')
        + '</div>'
        + '<div class="aftersell-card-body">'
        + vendor
        + '<p class="aftersell-card-title">' + p.title + '</p>'
        + '<p class="aftersell-card-price" data-price-el>' + priceHtml(p.price, p.compareAtPrice) + '</p>'
        + '</div></a>'
        + '<div class="aftersell-card-actions">'
        + selectHtml
        + atcBtn
        + '</div></div></li>';
    }).join('');

    attachCardEvents();
    initSplide();
  }

  function attachCardEvents() {
    list.addEventListener('change', function(e) {
      if (!e.target.classList.contains('aftersell-variant-select')) return;
      var select  = e.target;
      var card    = select.closest('.aftersell-card');
      var opt     = select.options[select.selectedIndex];
      var priceEl = card.querySelector('[data-price-el]');
      var btn     = card.querySelector('.aftersell-cta');
      priceEl.innerHTML     = priceHtml(opt.getAttribute('data-price'), opt.getAttribute('data-compare'));
      btn.dataset.variantId = opt.value;
      btn.disabled          = false;
      btn.textContent       = CTA_LABEL;
      btn.className         = 'aftersell-cta';
    });

    list.addEventListener('click', function(e) {
      var btn = e.target.closest('.aftersell-cta');
      if (!btn || btn.disabled) return;
      var variantId = btn.dataset.variantId;
      if (!variantId) return;

      e.preventDefault();
      btn.disabled    = true;
      btn.textContent = 'Adding…';

      fetch('/cart/add.js', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: parseInt(variantId, 10),
          quantity: 1,
          properties: {
            "_source": "Aftersell",
            "_attribution": "CTA"
          }
        })
      })
      .then(function(r) {
        if (!r.ok) throw new Error('Cart add failed: ' + r.status);
        return r.json();
      })
      .then(function() {
        btn.textContent = 'Added!';
        btn.classList.add('aftersell-cta--added');
        if (typeof window.theme !== 'undefined' && window.theme.cart && window.theme.cart.open) {
          window.theme.cart.open();
        }
        setTimeout(function() {
          btn.textContent = CTA_LABEL;
          btn.classList.remove('aftersell-cta--added');
          btn.disabled = false;
        }, 2500);
      })
      .catch(function(err) {
        console.error('[AfterSell carousel] Add to cart error:', err);
        btn.textContent = 'Try Again';
        btn.disabled    = false;
      });
    });
  }

  function initSplide() {
    function mount() {
      if (typeof window.Splide === 'undefined') {
        setTimeout(mount, 50);
        return;
      }

      splideInstance = new Splide('#aftersell-splide-' + SECTION_ID, {
        type:       'slide',
        perPage:    4,
        perMove:    4,
        gap:        '10px',
        pagination: false,
        arrows:     false,
        speed:      350,
        drag:       false,
        breakpoints: {
          768: { perPage: 2, perMove: 2, drag: 'free', snap: true },
          480: { perPage: 1, perMove: 1, drag: 'free', snap: true, padding: { right: '25%' } }
        }
      }).mount();

      function updateArrows() {
        var idx = splideInstance.index;
        var end = splideInstance.length - splideInstance.options.perPage;
        prevBtn.disabled = idx <= 0;
        nextBtn.disabled = idx >= end;
      }

      prevBtn.addEventListener('click', function() { splideInstance.go('<'); });
      nextBtn.addEventListener('click', function() { splideInstance.go('>'); });
      splideInstance.on('moved', updateArrows);
      updateArrows();
    }
    mount();
  }

  function clearSkeletons() { list.innerHTML = ''; }

  evaluate();
})();
</script>

{% endif %}

{% schema %}
{
  "name": "Aftersell Carousel",
  "settings": [
    { "type": "text",  "id": "strategy_id",  "label": "AfterSell Strategy ID", "default": "ADD_ID_HERE" },
    { "type": "text",  "id": "heading",      "label": "Heading",               "default": "You might also like" },
    { "type": "text",  "id": "cta_label",    "label": "CTA Button Label",      "default": "Add to cart" },
    { "type": "range", "id": "max_products", "label": "Max Products to Show",  "default": 8, "min": 1, "max": 20, "step": 1 }
  ],
  "presets": [{ "name": "Aftersell Carousel" }]
}
{% endschema %}
```

<Frame>
  <img src="https://mintcdn.com/aftersell/SnVX3h-PpMMxQMDU/images/aftersell/strategy-carousel-example.png?fit=max&auto=format&n=SnVX3h-PpMMxQMDU&q=85&s=8fb7ac08c2ae0c430b5bc4f810c59e40" alt="Ein Strategy-gestütztes Produktkarussell auf einer Shopify-Produktseite" width="2692" height="1146" data-path="images/aftersell/strategy-carousel-example.png" />
</Frame>

<div id="customizing">
  ### Anpassen
</div>

Das Section-Schema stellt vier vom Händler bearbeitbare Einstellungen bereit: **Strategy ID**, **Heading**, **CTA Button Label** und **Max Products to Show**. Füge im `{% schema %}`-Block Einstellungen hinzu oder entferne sie, um dem Theme-Editor mehr Stellschrauben zu geben.

Das CSS ist unter `.aftersell-*`-Klassennamen gescoped und enthält ein Splide-gesteuertes Karussell mit 4 Produkten pro Ansicht, das bei 768px auf 2 und bei 480px auf 1 wechselt. Passe es beliebig an dein Theme an — nichts davon ist für den API-Aufruf erforderlich.

***

<div id="cart-context-custom-cart-upsell-block">
  ## Warenkorb-Kontext: individueller Warenkorb-Upsell-Block
</div>

Dieses Muster ist strukturell identisch mit dem PDP-Muster, mit einem entscheidenden Unterschied: Das **Produktkontext-Array** wird aus den Positionen des Warenkorbs statt aus dem aktuell angezeigten Produkt gebildet. Die Strategy erhält dann jeden Artikel, den der Käufer hinzugefügt hat, und gibt Empfehlungen auf Basis des gesamten Warenkorbs zurück.

Die Implementierung befindet sich dort, wo dein individueller Warenkorb-Code lebt — eine Liquid-Section, die den Cart Drawer rendert, ein individueller Block in einer Headless-Storefront oder ein Theme-Template wie `cart.liquid`. Die Form des API-Aufrufs und die Verarbeitung der Antwort sind identisch mit dem PDP-Beispiel — nur das `products`-Array unterscheidet sich.

Die Struktur sieht so aus:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
var products = {{ cart.items | json }}.map(function(item) {
  return {
    productId: 'gid://shopify/Product/' + item.product_id,
    variantId: 'gid://shopify/ProductVariant/' + item.variant_id,
    quantity:  item.quantity,
    price:     item.price / 100
    // ...other fields as needed
  };
});
```

Der Rest des Payloads (cart, customer, session, cartToken) und der `fetch`-Aufruf an `/api/public/strategy/evaluate` bleiben gegenüber dem obigen PDP-Muster unverändert — nur das `products`-Array wechselt von `[productContext]` zum aus dem Warenkorb abgeleiteten Array.

***

<div id="what-happens-when-the-strategy-returns">
  ## Was passiert, wenn die Strategy antwortet
</div>

Die Form der Antwort ist unabhängig davon, welchen Kontext du gesendet hast:

```json theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
{
  "success": true,
  "products": [ /* enriched recommended products */ ],
  "evaluationId": "f3a1c2e0-...",
  "resolution": { "strategyId": "...", "matchedRuleIds": [...], "fallbackUsed": false },
  "meta": { "servedFromCache": false, "processingTimeMs": 12, "data": {} }
}
```

Die `evaluationId` ist eine eindeutige ID für diese Auswertung. Wenn du sie erfasst und an die gerenderten Produkte anhängst, kannst du die resultierende Bestellung genau der Empfehlung zuordnen, die sie hervorgebracht hat — siehe [Attribution](#attribution) weiter unten.

Wie du das `products`-Array renderst, liegt vollständig bei deinem Theme-Code. Das obige PDP-Snippet rendert die Produkte als Karussell aus Karten mit Variantenauswahl und Add-to-Cart-Buttons; ein individueller Warenkorb-Block könnte sie als vertikale Liste im Drawer rendern.

Das vollständige Request- und Response-Schema findest du in der [Evaluate-Strategy-API-Referenz](/de/aftersell/strategies_api_reference_evaluate_strategy).

***

<div id="when-no-product-is-returned">
  ## Wenn kein Produkt zurückgegeben wird
</div>

Wenn die Strategy keine Produkte zurückgibt (`products: []`), entscheidet dein Code, wie damit umgegangen wird. Das obige PDP-Snippet blendet das Karussell komplett aus. Ein individueller Warenkorb-Block könnte auf die Standard-Upsell-Liste des Warenkorbs zurückfallen oder einfach nichts rendern.

Um eine leere Antwort zu vermeiden, konfiguriere in der Strategy einen **Catch all**, damit immer ein Fallback-Produkt zurückgegeben wird. Wie du einen Catch all einrichtest, erfährst du auf der Seite [Strategies erstellen](/de/aftersell/strategies_building_in_app).

***

<div id="tips-for-custom-integrations">
  ## Tipps für individuelle Integrationen
</div>

* **Kontext in Liquid aufbauen.** Liquid läuft zur Renderzeit und hat Zugriff auf den vollständigen Shopify-Objektgraphen — product, cart, customer, shop, request. Nutze es, um den Payload serverseitig zu befüllen, statt auf clientseitige Aufrufe zurückzugreifen.
* **API-Key nicht in öffentliche Repos legen.** Er landet in deinem Theme-Code, der an den Browser ausgeliefert wird — das ist in Ordnung. Aber füge dasselbe Theme nicht in ein öffentliches Repository ein und teile das Bundle nicht extern.
* **Catch all verwenden.** Storefront-Erlebnisse wirken kaputt, wenn ein Slot verschwindet. Ein Catch all mit einer kleinen Auswahl sicherer Standardprodukte hält die UI konsistent.
* **Cachen, wo es sinnvoll ist.** Die Strategies API cached serverseitig leicht (`meta.servedFromCache`), aber bei stark frequentierten PDPs möchtest du Aufrufe möglicherweise zusätzlich clientseitig debouncen oder memoizen (z. B. nicht erneut aufrufen, wenn dasselbe Produkt in einer Session zweimal gerendert wird).

***

<div id="attribution">
  ## Attribution
</div>

Wenn ein Käufer im Snippet auf den Add-to-Cart-Button klickt, hängt der `/cart/add.js`-Aufruf **Line-Item-Properties** an den Warenkorbartikel an:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
properties: {
  "_source": "Aftersell",
  "_attribution": "CTA"
}
```

Diese Properties begleiten die Position bis zur Shopify-Bestellung, wo sie im Line-Item-Datensatz erscheinen. Du kannst sie nachgelagert nutzen, um Umsätze zu attribuieren, Bestellungen zu filtern oder Analysetools zu füttern, die Line-Item-Properties lesen.

Die Keys und Werte sind Konventionen, keine Anforderungen — der API-Aufruf funktioniert unabhängig davon, was du hier einträgst. Passe sie an dein eigenes Attributionsmodell an. Zum Beispiel:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
properties: {
  "_source": "PDP Carousel",
  "_strategy_id": "{{ section.settings.strategy_id }}",
  "_campaign": "summer-2026"
}
```

<Tip>
  Property-Keys, die mit einem Unterstrich (`_`) beginnen, werden im Warenkorb und im Checkout-UI ausgeblendet, hängen aber trotzdem an der Bestellung. Verwende das Unterstrich-Präfix für reine Attributions-Metadaten, die Käufer nicht sehen sollen.
</Tip>

Wende dasselbe Muster in der Warenkorb-Kontext-Implementierung an — jeder Add-to-Cart-Aufruf aus einem individuellen Upsell-Block kann beliebige Properties mitführen.

<div id="attributing-back-to-the-evaluation">
  ### Zuordnung zur Auswertung
</div>

Um eine Bestellung der **exakten Auswertung** zuzuordnen, die das Produkt empfohlen hat — statt nur „kam von einer Strategy“ —, erfasse die `evaluationId` aus der Antwort und hänge sie unter der Property `__as_offer_id` an die Position an. AfterSell liest diesen Key, sodass damit getaggte Bestellungen im Reporting der spezifischen Auswertung zugeordnet werden.

Halte im `evaluate()`-Handler die ID aus der Antwort fest:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
.then(function(data) {
  if (!data || !data.success || !data.products || !data.products.length) {
    clearSkeletons(); return;
  }
  evaluationId = data.evaluationId; // declare `var evaluationId;` alongside your other state
  renderCards(data.products.slice(0, MAX_PRODUCTS));
})
```

Nimm sie dann in die Add-to-Cart-Properties auf:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
properties: Object.assign(
  { "_source": "Aftersell", "_attribution": "CTA" },
  evaluationId ? { "__as_offer_id": evaluationId } : {}
)
```

Behalte den doppelten Unterstrich bei `__as_offer_id` bei — das ist der Key, nach dem AfterSell sucht, und das Unterstrich-Präfix hält ihn vor Käufern verborgen. Wenn `evaluationId` fehlt (zum Beispiel weil keine Produkte zurückgegeben wurden), lass die Property weg, statt einen leeren Wert zu senden.
