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 denprojects-Präfix. - Eigentümerschaft: Der API-Schlüssel muss dem Projekteigentümer gehören;
projectUidundintegrationIdmü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 StatusPUBLISHEDzurück. Dashboard-APIs können weiterhin alle Status für Eigentümer abrufen. - IndexNOW: Optionales
config.indexNowauf 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 zeigenindexNowStatus. - Produktfelder (Dashboard): Kanonische Produkt- und Variantenfelder werden in
attributesgespeichert; optionale sprachspezifische Überlagerungen befinden sich inlocales.<code>(nameundattributesin jedem Eintrag).nameist ein Base-Typ-Attribut auf Produktdatensätzen;skuist ein erforderliches Base-Typ-Attribut, das als Variantenachse markiert ist (auf Varianten gespeichert, voneffective-price-Lookup verwendet). - Produkt-JSON (nur externe Liste/Detail/Varianten): Antworten enthalten kein
locales-Objekt.attributesauf 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-Localenamewird auf denname-Attributschlüssel abgebildet). Aufgelöstes Locale = optionale Abfragelocale=<code>wenn vorhanden und gültig (z. B.en,de,pt-br); andernfalls ``defaultCatalogLocale`` aus derconfigder MARKET-Integration (im Dashboard-Bildschirm Edit integration gesetzt); wenn nicht gesetzt, ``en``. Ungültigelocale-Abfrage gibt 400 zurück. - Produktfelder (extern): Nach der Zusammenführung gelten dieselben Regeln wie im Dashboard für die abgeflachten
attributes(einschließlichname-/sku-Semantik oben). - ID-Format: MARKET-Ressourcen-IDs in Anfrageparametern und Antwortnutzlasten akzeptieren und geben sowohl
cuid- als auchcuid2-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 mituseAsRoot = trueals Katalog-Stammkategorien behandeln (mehrere Tags erlaubt), auch wennparentTagIdnicht 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.appoder 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, keinlocales). Optionale Abfrage ``locale`` (siehe Übersicht).GET …/products/{productId}--- Ein Produkt (gleicher Vertrag).GET …/products/{productId}/variants--- Nur Varianten; jede Zeile hat zusammengeführteattributesund keinlocales. Optionales ``locale``.GET …/products/{productId}/price-range--- Für ein veröffentlichtes Produkt: effektiver Stückpreis pro Variante (minAmount/maxAmountsind 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 …/tagsundGET …/tags/{tagId}--- Katalog-Tags.GET …/price-listsundGET …/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-listsundGET …/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 („verwendebasefü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 <= atwerden 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
priceKindIdnicht 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-listsund 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_TEXToptions: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. ). 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:
schemaVersionproduct(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/validatevor 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 deklariertenoptionsentsprechen- 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}/ordersmit 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"}