Marktkatalog --- externe Lese-API

Zuletzt aktualisiert: Aug 31, 2026Abschnitt: Integrationen

Vollständige API-Referenz: https://api.mailoo.app/docs/v1

Verwenden Sie eine Market-Integration, um einen minimalen Produktkatalog im Mailoo-Dashboard zu verwalten und schreibgeschütztes JSON an Ihren Onlineshop oder BFF bereitzustellen. Jede externe Route erfordert ``X-API-Key``; es gibt keinen anonymen Zugang.

Für Dashboard-CSV-Massenbearbeitung (Export → bearbeiten → hochladen → Vorschau/Anwenden) siehe market-catalog-csv{.interpreted-text role="doc"}.

Übersicht

  • Authentifizierung: Header ``X-API-Key`` bei jeder Anfrage.
  • Berechtigung: RESTRICTED-Schlüssel benötigen ``market.external-read`` in allowedOperations (FULL-Schlüssel funktionieren ebenfalls).
  • Routing: GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/… --- gleiche Pfadform wie Dashboard-Ressourcen unter /projects/{uid}/integrations/{id}/market/…, ohne den projects-Präfix.
  • Eigentümerschaft: Der API-Schlüssel muss dem Projekteigentümer gehören; projectUid und integrationId müssen auf eine ACTIVE-Integration vom Typ MARKET verweisen.
  • Sichtbarkeit: Produkte haben den Status DRAFT/PUBLISHED/ARCHIVED. Die externe API für Liste/Detail/Varianten gibt nur Produkte mit Status PUBLISHED zurück. Dashboard-APIs können weiterhin alle Status für Eigentümer abrufen.
  • IndexNOW: Optionales config.indexNow auf der Market-Integration --- Mailoo sendet Produkt-IDs per POST an Ihre Notify-URL bei Veröffentlichung/Änderung; Sie übermitteln URLs an IndexNOW (indexnow-notify{.interpreted-text role="doc"}). Inhaltsdatensätze zeigen indexNowStatus.
  • Produktfelder (Dashboard): Kanonische Produkt- und Variantenfelder werden in attributes gespeichert; optionale sprachspezifische Überlagerungen befinden sich in locales.<code> (name und attributes in jedem Eintrag). name ist ein Base-Typ-Attribut auf Produktdatensätzen; sku ist ein erforderliches Base-Typ-Attribut, das als Variantenachse markiert ist (auf Varianten gespeichert, von effective-price-Lookup verwendet).
  • Produkt-JSON (nur externe Liste/Detail/Varianten): Antworten enthalten kein locales-Objekt. attributes auf dem Produkt und auf jeder Variante sind bereits zusammengeführt für eine einzelne Sprache: Basiswerte aus dem Dashboard-Datensatz plus die Überlagerung für das aufgelöste Locale (pro-Locale name wird auf den name-Attributschlüssel abgebildet). Aufgelöstes Locale = optionale Abfrage locale=<code> wenn vorhanden und gültig (z. B. en, de, pt-br); andernfalls ``defaultCatalogLocale`` aus der config der MARKET-Integration (im Dashboard-Bildschirm Edit integration gesetzt); wenn nicht gesetzt, ``en``. Ungültige locale-Abfrage gibt 400 zurück.
  • Produktfelder (extern): Nach der Zusammenführung gelten dieselben Regeln wie im Dashboard für die abgeflachten attributes (einschließlich name-/sku-Semantik oben).
  • ID-Format: MARKET-Ressourcen-IDs in Anfrageparametern und Antwortnutzlasten akzeptieren und geben sowohl cuid- als auch cuid2-Werte zurück (z. B. Tag-/Produkt-/Preislisten-/Lieferanten-IDs).
  • Preisarten: Siehe Preisarten und effektiver Preis unten. Für welche Listenpreisart zu verwenden ist, sollten Integratoren den Preisarten-Schlüssel (key) konfigurieren und senden (Abfrage ``kind`` auf ``GET .../effective-price``). Die Preisarten-ID (CUID) ist intern (und erscheint in manchen JSON); sie ist nicht der Hauptidentifikator für diese Auswahl.
  • Integrator-Root-Flag: Tag-Objekte enthalten useAsRoot. Integratoren können Tags mit useAsRoot = true als Katalog-Stammkategorien behandeln (mehrere Tags erlaubt), auch wenn parentTagId nicht null ist.

Tag-Icons: Tag-Objekte können iconMediaId enthalten. Um Bildbytes abzurufen, verwenden Sie die Bilder-Integration mit einem Schlüssel, der ``image.external-read`` erlaubt (siehe images{.interpreted-text role="doc"}).

Mailoo-Dashboard-Rendering-Hinweis: useAsRoot ist für die externe Integrator-Menülogik gedacht. Das Dashboard-Tag-Baum-Rendering und die Sortierung bleiben unverändert und folgen weiterhin den bestehenden Hierarchieregeln.

BFF-Umgebungsvariablen

Abgestimmt auf das Connection-Panel der Integrationsseite (gleiche Namen wie unten). Typische serverseitige Variablen:

  • MAILOO_MARKET_API --- API-Basis-URL (z. B. https://api.mailoo.app oder Ihr Entwicklungshost), kein abschließender Schrägstrich.
  • MAILOO_MARKET_API_KEY --- API-Schlüssel mit ``market.external-read`` (oder FULL).
  • MAILOO_MARKET_PROJECT_UID --- Projekt-UID.
  • MAILOO_MARKET_INTEGRATION_ID --- Market-Integrations-ID (CUID).

Endpunkte (alle GET, alle erfordern X-API-Key)

Ersetzen Sie {baseUrl}, {projectUid}, {integrationId} und Pfadparameter nach Bedarf.

  • GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/types --- Produkttypen und Attribute.
  • GET …/products --- Alle veröffentlichten Produkte (Varianten und Tag-Links enthalten; zusammengeführtes Locale, kein locales). Optionale Abfrage ``locale`` (siehe Übersicht).
  • GET …/products/{productId} --- Ein Produkt (gleicher Vertrag).
  • GET …/products/{productId}/variants --- Nur Varianten; jede Zeile hat zusammengeführte attributes und kein locales. Optionales ``locale``.
  • GET …/products/{productId}/price-range --- Für ein veröffentlichtes Produkt: effektiver Stückpreis pro Variante (minAmount / maxAmount sind gleich für eine einzelne Preisart) plus optionale produktweite Zeile und ein Aggregat Min/Max über diese Zahlen. Abfrage: ``kind`` oder ``priceKindId`` (gleiche Regeln wie ``effective-price``), optionales ``measurementUnitId``, ``at`` (ISO-Datetime), optionales ``currency`` (ISO 4217, drei Buchstaben). Währungsauflösung: wenn ``currency`` weggelassen wird, stammt die Antwortwährung aus der ersten übereinstimmenden Preiszeile beim Durchlaufen aktiver Listen in chronologischer Reihenfolge (``effectiveAt`` aufsteigend) und Zeilen in ``id``-Reihenfolge; nur Zeilen in dieser Währung werden für Zusammenführungen verwendet (andere Währungen werden ignoriert). Wenn ``currency`` gesetzt ist, nehmen nur Zeilen in dieser Währung teil; das Antwortfeld ``currency`` gibt die Abfrage zurück, auch wenn keine übereinstimmenden Zeilen vorhanden sind (dann sind die Beträge null).
  • GET …/tags und GET …/tags/{tagId} --- Katalog-Tags.
  • GET …/price-lists und GET …/price-lists/{priceListId} --- Interne Preislisten.
  • GET …/effective-price --- Stückpreis für ein Produkt und eine Preisart auflösen. Verwenden Sie die Abfrage ``kind=<priceKindKey>`` (siehe Preisarten und effektiver Preis). Optional: productId / variantId / measurementUnitId / at (ISO-Datetime). (Abfrage ``priceKindId`` wird für interne oder BFF-Aufrufer akzeptiert; externer Katalogcode sollte ``kind`` verwenden.)
  • GET …/suppliers --- Lieferanten.
  • GET …/suppliers/{supplierId}/products --- Lieferantenprodukte.
  • GET …/suppliers/{supplierId}/price-lists und GET …/suppliers/{supplierId}/price-lists/{supplierPriceListId} --- Lieferanten-Preislisten.
  • GET …/units --- Maßeinheiten.
  • GET …/packagings --- Verpackungen.
  • GET …/packaging-equivalences --- Verpackungsäquivalenzen.

Antworten verwenden { success: true, data: … }. Für externe Produktliste/Detail/Varianten entspricht data der zusammengeführten Locale-Form oben (kein locales). Authentifizierte Dashboard-GET /api/v1/projects/…/market/products…-Aufrufe geben weiterhin die vollständige Editor-Form einschließlich locales zurück.

Preisarten und effektiver Preis

  • In der Datenbank hat eine Preisart einen stabilen String-``key`` (eindeutig pro MARKET-Integration, z. B. base) und eine interne ``id`` (CUID). Der key ist das, was Sie in Umgebung / CMS konfigurieren sollten („verwende base für Listenpreise").
  • Preisauflösung aus externem oder Storefront-Code: rufen Sie ``GET {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/effective-price`` mit ``kind=<key>`` und ``productId=`` auf (und optionalem ``variantId``, ``measurementUnitId``, ``at``). Dies ist der unterstützte Vertrag: ``kind`` ist der Preisarten-Schlüssel, nicht die CUID. Alle aktiven Preislisten mit effectiveAt <= at werden in chronologischer Reihenfolge zusammengeführt (älteste zuerst, dann neuere Listen überlagern): für eine gegebene ``variantId`` gewinnt die neueste variantenspezifische Zeile für diese SKU; wenn keine spätere Liste diese SKU neu definiert, bleiben ältere SKU-Zeilen wirksam. Wenn es überhaupt keine SKU-Zeile gibt, gilt die neueste produktweite Zeile (``variantId`` null). Wenn ``variantId`` weggelassen wird, werden nur produktweite Zeilen berücksichtigt (neueste gewinnt).
  • ``priceKindId`` im Query-String ist nur für Aufrufer, die bereits CUIDs speichern (Mailoo-Tools, BFFs mit internen IDs). Behandeln Sie den Abfrageparameter priceKindId nicht als die externe Haupt-API für „welcher Listenpreis" --- verwenden Sie stattdessen ``kind``.
  • Antworten können ``priceKindId`` und ``priceKindKey`` enthalten (und ähnliches auf Preislistenzeilen). Sie dürfen ``priceKindKey`` lesen; ``priceKindId`` in Antworten dient der Korrelation und muss nicht in öffentlichem Frontend-JS hartcodiert werden.
  • Preislisten-Lebenszyklus (Lesepfade): GET …/price-lists und Listen-Nutzlasten enthalten ``archivedAt`` und ``endsAt`` wenn gesetzt. ``GET .../effective-price``, ``GET .../products/{productId}/price-range`` und Bestell-Checkout-Pricing berücksichtigen nur Listen, die nicht archiviert sind und, wenn ``endsAt`` gesetzt ist, nur wenn der Abfragezeitpunkt ``at`` strikt vor ``endsAt`` liegt. Dashboard-Aufrufer können interne Preislisten per PATCH / DELETE unter ``/api/v1/projects/.../market/price-lists/{priceListId}`` (Bearer) ändern; Löschen wird mit 409 abgelehnt wenn Dimensionen einer Zeile auf einer bestehenden Bestellzeile für diese Integration vorkommen.

Produktattribut-Werttypen (/types)

GET …/types gibt Typattribute zurück mit:

  • valueType: STRING, NUMBER, BOOLEAN, ENUM, SELECT, MD_TEXT
  • options: string[] | null

SELECT ist der strikte listenbasierte Typ. Für SELECT ist options eine nicht-leere Liste erlaubter Werte, und Produkt-/Variantenattributwerte müssen exakt einem davon entsprechen.

``MD_TEXT`` und Bilder: Jedes Produkt- oder Variantenattribut mit valueType: MD_TEXT kann Mailoo-gehostete Bilder über Markdown-Bildsyntax einbetten (z. B. ![alt](url)). Beim Schreiben normalisiert das Dashboard Mailoo-Bild-URLs innerhalb dieser Felder; Produkt- und Varianten-JSON aus GET-Antworten normalisiert ebenfalls verschachtelte Strings, sodass Integratoren die kanonische öffentliche Einbettungsform sehen (…/api/v1/images/public/content?id=<mediaId>). Um Bildbytes von dieser URL abzurufen, verwenden Sie die Bilder-API mit einem Schlüssel, der ``image.external-read`` erlaubt (siehe images{.interpreted-text role="doc"}).

``MD_TEXT`` und HTML (nur externes Katalog-GET): Auf externen Routen GET …/products, GET …/products/{productId} und GET …/products/{productId}/variants (mit X-API-Key und einem veröffentlichten Produkt) fügt die API ein paralleles String-Feld <attributeKey>Html neben jedem Markdown-Wert für Attribute hinzu, die auf einem der verknüpften Produkttypen als MD_TEXT deklariert sind, auf den zusammengeführten Produkt-attributes und den zusammengeführten attributes jeder Variante (kein separater locales-Baum in der Antwort). Verwenden Sie den Originalschlüssel für die Markdown-Quelle (wie Blog-content) und …Html für servergerendertes HTML (wie Blog-htmlContent). Dashboard-GET /projects/…/market/…-Antworten lassen diese *Html-Felder weg, um beim Bearbeiten zusätzliche Arbeit zu vermeiden.

Produktgalerie und Listenbild: Jedes Produkt kann ein geordnetes ``images``-Array haben (Objekte mit id, mediaId, sortOrder und kanonischer ``url``) plus ``previewMediaId`` und ``previewImageUrl``. Die Vorschau ist das für Katalogkarten / Produktlisten empfohlene Bild; wenn ``previewMediaId`` null ist, können Integratoren auf das erste Galeriebild zurückgreifen. Galerie-Media-IDs werden beim Erstellen/Aktualisieren validiert (READY-Medien, im Kontext des USER-/PROJECT-/INTEGRATION-Bereichs des Eigentümers für diesen Markt). Optionale On-the-fly-Größenanpassung: hängen Sie ``w``, ``h``, ``fit``, ``format``, ``q`` an die öffentliche Inhalts-URL an, wie in images{.interpreted-text role="doc"} dokumentiert.

Beispiel-Attributdefinition:

{
  "key": "display_type",
  "name": "Display Type",
  "valueType": "SELECT",
  "options": ["LCD", "OLED", "micro-OLED", "QD-LCD"]
}

Hinweis zu Breaking Changes: Integrationen, die ENUM bisher als Freitext-String behandelt haben, sollten auf SELECT umstellen wenn eine strikte Optionsliste erforderlich ist.

Dashboard: Produkte aus JSON erstellen

Im MARKET-Dashboard (Abschnitt Products) können Sie Produktnutzlasten als JSON vorbereiten:

  • Copy template --- kopiert den aktuellen Produktentwurf + verfügbare Schema-Hinweise:
  • schemaVersion
  • product (typeIds, attributes, tagIds)
  • template.attributeSchema (Schlüssel, Werttypen, Pflichtflags, SELECT-Optionen)
  • template.availableTags (alle aktuell verfügbaren Katalog-Tags)
  • Paste product data --- schaltet das Formular in einen JSON-Eingabemodus, in dem Sie die Nutzlast manuell einfügen (keine Browser-Zwischenablage-Leseaufforderung erforderlich).
  • Create Product (im JSON-Modus) validiert die Nutzlast auf dem Server und erstellt dann das Produkt bei erfolgreicher Validierung.

Für Massenaktualisierungen bevorzugen Sie CSV-Upload/-Download: market-catalog-csv{.interpreted-text role="doc"}.

Erwartete JSON-Nutzlast

{
  "schemaVersion": 1,
  "product": {
    "typeIds": ["<typeId-1>", "<typeId-2>"],
    "attributes": {
      "name": "Vision Pro X Ultra",
      "display-type": "micro-OLED",
      "short_description": "Flagship model.",
      "full_description": "## Vision Pro X Ultra"
    },
    "tagIds": ["<tagId-1>"]
  }
}

Validierungsverhalten

Serverseitige Validierung ist die einzige Wahrheitsquelle (keine duplizierten Domänenregeln auf dem Client):

  • Dashboard ruft POST /api/v1/projects/{uid}/integrations/{id}/market/products/validate vor dem Erstellen auf.
  • Der Endpunkt verwendet dieselben Validierungsregeln wie die Produkterstellung:
  • unbekannte Attributschlüssel werden abgelehnt
  • Pflichtattribute müssen vorhanden sein
  • Werttypen müssen übereinstimmen (NUMBER/BOOLEAN/STRING/MD_TEXT/ENUM/SELECT)
  • SELECT-Werte müssen einer der deklarierten options entsprechen
  • Tag-IDs müssen zur Integration gehören
  • Wenn die Validierung fehlschlägt, wird das Produkt nicht erstellt und die API-Fehlermeldung wird angezeigt.

E-Mail-Checkout (Bestellungen)

Die oben beschriebene Katalog-Lese-API enthält keinen Warenkorb: Der Integrator-Onlineshop verwaltet den Warenkorbstatus (z. B. localStorage) und übermittelt beim Checkout einen Snapshot.

  • Bestellung erstellen (Server / BFF): POST {baseUrl}/api/v1/market/{projectUid}/integrations/{integrationId}/orders mit Header ``X-API-Key`` und Berechtigung ``market.order.submit`` (RESTRICTED-Schlüssel) oder einem FULL-Schlüssel. Anfragekörper: Käufer-``customerEmail``, ``items[]`` (jede Zeile: ``productId``, ``quantity`` als Dezimalstring, und entweder ``priceKindKey`` (integratorfreundlicher Schlüssel, z. B. base) oder ``priceKindId`` (CUID) --- nicht beides, optionales ``variantId`` / ``measurementUnitId``). Mailoo löst Stückpreise aus denselben Preislisten wie ``GET .../effective-price`` auf; die Antwort enthält ``accessToken`` und ``accessExpiresAt`` (temporärer schreibgeschützter Zugriff auf die Bestellung).
  • Bestellung lesen (Browser / beliebiger Client): GET {baseUrl}/api/v1/market/public/orders/{orderId}?token= oder Header ``X-Market-Order-Token`` --- kein API-Schlüssel. Gibt einen Snapshot der Bestellung zurück, solange das Token gültig ist (Standard-TTL 72 Stunden, überschreibbar über ``MARKET_ORDER_ACCESS_TOKEN_TTL_HOURS`` auf dem Mailoo-Host). Fehlendes/ungültiges/abgelaufenes Token gibt 404 zurück (gleiche Meldung wie falsche ID).
  • Eigentümer-Dashboard (JWT): Bestellungen auflisten/abrufen/patchen unter /api/v1/projects/{uid}/integrations/{id}/market/orders/… (Bearer).

End-to-End-Ablauf, BFF-Muster und Betriebshinweise: market-order-email-checkout{.interpreted-text role="doc"}.

Weiterführende Lektüre

  • OpenAPI: https://api.mailoo.app/docs/v1
  • Dashboard-CSV-Workflow: market-catalog-csv{.interpreted-text role="doc"}