Zum Inhalt

PlentyOne API @ Mokebo — Guide für Fabian

Audience: Du willst über die PlentyOne-REST-API arbeiten (lesen, schreiben — in deinem Fall Bilder). Diese Anleitung dokumentiert wie wir das bei Mokebo aktuell machen, gegroundet auf zwei produktiv laufende Workflows. Keine Theorie, keine Spekulation — wenn etwas hier steht, machen wir es exakt so.

Stand: Mai 2026. Quellen: SUB PlentyOne Lookup, 01 Retouren-Erfassung, 00 Stack Heartbeat — Connection Test.


1. Setup — was bei Mokebo schon da ist

Item Wert
Hostname p38991.my.plentysystems.com
API-User n8n-api (REST-API-User, kein Backend-Login)
n8n-Instanz https://ai-mokebo.app.n8n.cloud/
n8n-Projekt Susi Projects — Workflow + Credential müssen im selben Projekt liegen, sonst sieht der Custom-Auth-Dropdown die Credential nicht
Credential Plenty Login Body — Custom Auth Credential. Liegt in Susi Projects und ist geshared. Enthält Username + Passwort als JSON-Body-Injection.

Du brauchst nichts neu anzulegen. Die Credential existiert, jeder Workflow im Susi Projects kann sie referenzieren.

Historie / Warum so: Bis 05.05.2026 lief das über Workspace-Variables ($vars.PLENTY_USER/$vars.PLENTY_PASSWORD). Die sind aber ein n8n-Pro-Feature — auf unserem Cloud-Starter-Plan werden sie zu undefined aufgelöst und die Liste ist read-only. Custom Auth Credential ist der Workaround und bleibt der neue Standard.


2. Der Login-Node — exakt wie wir es machen

Knall das genau so in jeden neuen Plenty-Workflow rein. Das ist der identische Node aus SUB PlentyOne Lookup und 01 Retouren-Erfassung:

Setting Wert
Node-Typ HTTP Request
Node-Name Login PlentyOne ← bitte exakt so, andere Nodes referenzieren den Namen
Method POST
URL https://p38991.my.plentysystems.com/rest/login
Authentication Generic Credential Type
Generic Auth Type Custom Auth
Custom Auth Plenty Login Body (siehe Sektion 1)
Send Body true
Body Content Type JSON
Specify Body Using Fields Below
Body Parameters Leer lassen! Die Custom Auth Credential injiziert username + password automatisch. Wenn du hier zusätzlich Felder einträgst, kommt es zu Konflikten/Doppelsendung.

Was ist „Custom Auth"? Ein n8n-Credential-Typ, der ein freies JSON-Object ({"body": {...}} oder {"headers": {...}}) ins Request-Object merged. Bei uns enthält die Credential den Body mit Username + Passwort. Damit wandert das Passwort nirgends hardcodet durch den Workflow.

Output ($('Login PlentyOne').item.json):

{
  "tokenType": "Bearer",
  "accessToken": "eyJ...",
  "expiresIn": 86400,
  "refreshToken": "..."
}

accessToken ist gültig 24 Stunden. Wir cachen den Token nicht — jeder Workflow loggt sich bei jedem Run neu ein. Das ist die Mokebo-Konvention. Klingt verschwenderisch, ist aber bei <1000 Runs/Tag völlig egal und vermeidet komplexe Refresh-Logik.


3. Token in nachfolgenden Calls verwenden

In jedem weiteren HTTP-Request-Node setzt du einen einzigen Header:

Authorization: =Bearer {{ $('Login PlentyOne').item.json.accessToken }}

Das = davor ist die n8n-Syntax für „dieser Wert ist eine Expression". Kein anderer Header nötig (kein Accept, kein Content-Type — Plenty liefert per default JSON).

Beispiel-Request: einen Auftrag holen

GET https://p38991.my.plentysystems.com/rest/orders/{{ $json.orderId }}?with[]=addresses&with[]=orderItems

Headers:
  Authorization: =Bearer {{ $('Login PlentyOne').item.json.accessToken }}

4. Eager-Loading mit with[] — Pflicht-Wissen

Plenty-Endpunkte liefern per default nur die Kerndaten. Verschachtelte Felder (Adressen, Items, Properties) musst du explizit anfordern, sonst kommt das Feld als null zurück.

Macht mit with[]=feldname in den Query-Parametern. Mehrere Felder = with[] mehrmals:

?with[]=addresses&with[]=orderItems&with[]=orderReferences

Was wir produktiv eager-loaden:

with[]=... Was es lädt
addresses Alle Adressen (Rechnung + Lieferung) am Auftrag
addressRelations Mapping welche Adresse welcher Typ ist (Lieferadresse = typeId: 2)
orderItems Die Auftrags-Positionen
orderItems.properties Properties pro Position (verschachtelt!)
properties Auftrags-Properties (Marktplatz-Bestellnummer, externe IDs, …)
orderReferences Eltern-Kind-Beziehungen (Auftrag → Retoure → Gutschrift)
shippingPackages Versandpakete inkl. Tracking
documents PDFs am Auftrag

Verschachtelte Felder funktionieren mit Punkt-Notation: with[]=orderItems.properties lädt sowohl orderItems als auch jeweils ihre properties.

Pro-Tipp: Lieber 1× mit vielen with[] aufrufen als 5× ohne — Plenty-Calls zählen aufs Rate Limit, eager-Loading nicht extra.


5. Mokebo-spezifische Konstanten (das was sonst niemand weiß)

Diese IDs leben aktuell in den Code-Nodes der Production-Workflows. Du brauchst sie wahrscheinlich auch:

5.1 Auftragstypen (typeId / orderType)

ID Typ
1 Sales Order (Standard-Bestellung)
3 Retoure
4 Gutschrift

Für Filter: ?orderType=3 filtert auf Retouren.

5.2 Auftragsstatus (was wir filtern)

  • 8 = Storno → wir filtern den raus (statusId !== 8)
  • 9.01 bis 9.02 = aktive Retouren-Stati bei Mokebo. Filter: ?statusFrom=9.01&statusTo=9.02

5.3 Property typeId an Aufträgen / Order-Items

Plenty lagert viele Werte als Properties aus. Bei uns relevant:

typeId Bedeutung Wo
7 Marktplatz-Bestellnummer (externalOrderId als Property) am Auftrag (order.properties[])
35 Retourengrund (mokebo-spezifisch) am Order-Item (orderItems[].properties[])

Auslesen: (order.properties || []).find(p => p.typeId === 7)?.value

5.4 Address-Relations typeId

typeId Bedeutung
2 Lieferadresse

addressRelations ist die Verknüpfungs-Tabelle: [{addressId, typeId}, …]. Wenn du die Lieferadresse willst:

const deliveryRelation = (order.addressRelations || []).find(r => r.typeId === 2);
const deliveryAddress = order.addresses.find(a => a.id === deliveryRelation.addressId);

5.5 Verwendete Auftragsherkünfte (referrerId)

Aus dem Regex-Extraktor in SUB PlentyOne Lookup:

referrerId Kanal
2 Amazon
9 Shopify
14.00 XXXLutz
143.x Otto (referrerId beginnt mit 143)
152 bol.com
160.1 Otto (alte ID)
177.01 OBI

Custom-Filter im Workflow: String(entry.referrerId).startsWith('143') für „alle Otto-Aufträge".

5.6 Country-IDs (Standard PlentyOne)

Lebt im Code-Node der Retouren-Erfassung als COUNTRY_MAP. Die wichtigsten:

1=DE, 2=AT, 3=BE, 4=CH, 8=DK, 9=ES, 12=FR, 14=GB, 19=IE,
20=IT, 25=NL, 27=PL, 28=PT, 33=SE, 51=HR, 165=US

5.7 Retourengründe (mokebo-spezifisch)

39 IDs gemappt auf Klartext, lebt im Code-Node der Retouren-Erfassung als REASON_MAP. Bei Bedarf da rauskopieren — Susi pflegt das, wenn neue Gründe in Plenty hinzukommen.


6. Patterns aus den Production-Workflows

6.1 Auftrag per Plenty-ID holen

GET /rest/orders/{id}?with[]=addresses&with[]=orderItems&with[]=shippingPackages&with[]=documents&with[]=orderReferences

6.2 Auftrag per externer Order-ID (Marktplatz-Bestellnummer) finden

GET /rest/orders?externalOrderId={encodeURIComponent(externalId)}&with[]=...

Response ist {entries: [...]}. Filter clientseitig auf typeId === 1 und statusId !== 8, dann den neuesten nehmen.

⚠️ Falle: Bei Shopify-Bestellnummern (MKB-XXXXX) nicht über externalOrderId suchen — Plenty speichert dort die Shopify-interne ID, nicht das MKB-Label. Workaround: Per requester_email über Kontakte gehen.

6.3 Kontakt per E-Mail finden

GET /rest/accounts/contacts?contactEmail={encodeURIComponent(email)}

Response: {entries: [{id, ...}, ...]}. Erste ID nehmen.

6.4 Aufträge eines Kontakts holen

GET /rest/orders?contactId={id}&orderType=1&with[]=...

6.5 Retouren in Status-Range holen

GET /rest/orders?orderType=3&statusFrom=9.01&statusTo=9.02&itemsPerPage=50

6.6 Kommentare zu einem Auftrag holen

GET /rest/comments/order/{orderId}

Response ist direkt ein Array [{text, ...}, ...] (kein entries-Wrapper).

6.7 Retoure → Hauptauftrag verlinken

In der Retoure findest du die Verknüpfung über orderReferences:

const parentRef = (retoure.orderReferences || []).find(ref => ref.referenceType === 'parent');
const hauptauftragId = parentRef?.referenceOrderId;

7. Sub-Calls aus einem Code-Node

Wenn du in einem Code-Node (n8n) zusätzliche HTTP-Calls machen willst (z.B. weil du pro Item nochmal Plenty fragen musst), nutze this.helpers.httpRequest:

const token = $('Login PlentyOne').first().json.accessToken;

const order = await this.helpers.httpRequest({
  method: 'GET',
  url: `https://p38991.my.plentysystems.com/rest/orders/${orderId}?with[]=properties`,
  headers: { 'Authorization': `Bearer ${token}` },
  json: true,
});

Genau dieses Pattern läuft in 01 Retouren-Erfassung produktiv, pro Retourenauftrag werden 3 Sub-Calls gemacht (Hauptauftrag holen + Retoure-Items mit Properties + Comments). Funktioniert robust.


8. Pagination

Plenty paginiert fast jede Liste. Response sieht so aus:

{
  "page": 1,
  "totalsCount": 247,
  "isLastPage": false,
  "lastPageNumber": 5,
  "entries": [...]
}

Bei Mokebo nutzen wir itemsPerPage=50 (nicht das Maximum 250) — guter Balance-Punkt.

Für Schleifen: HTTP Request Node hat eingebautes Pagination-Feature unter Options → Pagination → Update Parameter in Each Request. Nutze das, dann brauchst du keinen Code-Loop. Stop-Bedingung: $response.body.isLastPage === true.


9. Naming-Konventionen bei Mokebo

Hält sich an alle existierenden Workflows:

Prefix Was
00 MAIN xxx Haupt-Workflows
01 xxx, 02 xxx, … Nummerierte Hauptflows
SUB xxx oder [SUB] xxx Subworkflows (Execute-Workflow-Trigger)
XX xx alte/deprecated Workflows
@xxx System-Workflows (z.B. @Execution Tracker)

Sticky Notes an jedem logischen Block. Code-Nodes mit Header-Kommentar (// === Was tut der Block ===).

Variables-Konvention bei Code-Nodes mit Plenty-Logik: - token aus $('Login PlentyOne').first().json.accessToken - IDs/Maps in CAPS (COUNTRY_MAP, REASON_MAP) - Source-of-truth-Kommentar dazu („Quelle: Plenty Backend → …")


10. Für deinen Use Case: Bilder hoch- und runterladen

⚠️ Honest disclosure: Wir haben noch nie Bilder über die Plenty-API geschrieben. Alle Workflows bei Mokebo lesen aktuell nur (Aufträge, Kontakte, Comments, Properties). Du betrittst hier Neuland. Diese Sektion ist Recherche-Pointer, kein verifiziertes Pattern.

10.1 Wichtige Vorab-Klärung

Bilder in PlentyOne hängen entweder am Artikel (= alle Varianten teilen das Bild) oder an einer spezifischen Variante (= z.B. die rote T-Shirt-Variante hat andere Fotos als die blaue). Das ändert den Endpunkt:

Use Case Endpunkt-Basis
Bild für gesamten Artikel /rest/items/{itemId}/images
Bild für eine Variante /rest/items/{itemId}/variations/{variationId}/images
Sichtbarkeit pro Marktplatz/Mandant/Sprache /rest/items/{itemId}/images/{imageId}/availabilities

→ Frag Susi welche Granularität du brauchst, bevor du anfängst.

10.2 Empfohlene Reihenfolge

  1. Erst lesen, dann schreiben. Mach einen GET auf /rest/items/{id}/images an einem existierenden Artikel um zu sehen wie die Response-Struktur aussieht. Notier dir das Format der Felder.
  2. Test-Artikel anlegen. Bevor du an echten Mokebo-Artikeln rumschreibst, lass dir von Susi einen Sandbox-Artikel anlegen oder geh in die Plenty-Test-Umgebung wenn ihr eine habt.
  3. Body-Format checken. Plenty unterstützt mehrere Upload-Modi:
  4. Binary upload (multipart/form-data)
  5. Base64 in JSON
  6. URL-Reference (Plenty zieht das Bild von einer URL) Welches du nehmen kannst, hängt davon ab wie deine Bilder vorliegen. URL-Reference ist meist am einfachsten in n8n.
  7. Doku zur Hand: developers.plentymarkets.com/en-gb/plentymarkets-rest-api/index.html — sucht nach „items.images". Postman-Collection auf github.com/plentymarkets/api-doc ist auch praktisch zum Probieren.
  8. Image-Availabilities nicht vergessen: Ein hochgeladenes Bild ist erstmal nicht im Webshop sichtbar. Du musst es zusätzlich pro Mandant/Marktplatz/Sprache sichtbar schalten via POST /rest/items/{itemId}/images/{imageId}/availabilities.

10.3 Wenn du Mokebo-spezifische IDs brauchst

  • Mandant-ID (plentyId): Bei Mokebo aktuell wahrscheinlich nur einer. Susi fragen, falls Default 0 nicht funktioniert.
  • Marktplatz-Sichtbarkeiten: Image-Availabilities arbeiten mit Referrer-IDs (siehe 5.5). Wenn du z.B. ein Bild nur für Shopify hochladen willst, brauchst du referrerId: 9.

11. Stolperfallen die wir tatsächlich getroffen haben

Problem Ursache Lösung
MKB-XXXXX per externalOrderId gibt nichts zurück Plenty speichert bei Shopify nicht das MKB-Label, sondern die Shopify-interne ID Per requester_email über Kontakte suchen
properties ist plötzlich null with[]=properties im Query vergessen Eager-Loading immer dazu
Token läuft mitten im langen Run ab Workflow läuft >24h Bei sehr langen Runs zwischendurch nochmal Login-Node aufrufen
addressRelations lädt aber addresses ist leer Beide eager loaden, nicht nur eins with[]=addresses&with[]=addressRelations
update_workflow aus n8n-MCP zerstört Credentials in allen Nodes newCredential('Name') matcht nicht mit existierenden Credentials gleichen Namens, sondern legt einen leeren Placeholder an Patches lieber in der UI machen, nicht via MCP
Login wirft 403/401 obwohl Passwort korrekt Plenty sperrt den n8n-api-User-Login automatisch nach mehrfach fehlgeschlagenen Versuchen (z.B. weil ein Workflow mit undefined retried oder ein Mensch manuell mit altem Passwort probiert hat). Cloud-Account-Login (account.plentysystems.com) geht trotzdem weiter — das ist eine separate Auth-Schicht und täuscht PlentyOne-Backend → Einrichtung → Einstellungen → Kontoverwaltung → Konten → n8n-api öffnen. Wenn unten links der Button LOGIN ENTSPERREN sichtbar ist → klicken, neues Passwort setzen, speichern, neues Passwort in der Plenty Login Body Credential aktualisieren
Custom-Auth-Dropdown im Login-Node sagt „No credentials yet" obwohl Credential existiert Workflow und Credential liegen in unterschiedlichen Projekten, oder n8n-Frontend-Cache ist veraltet Zuerst Hard-Reload (Cmd + Shift + R). Dann prüfen ob Workflow + Credential beide in Susi Projects liegen. Falls Credential in einem anderen Projekt: über Sharing-Tab teilen oder verschieben.

12. Cheat Sheet zum Kopieren

12.1 n8n: Login-Node-JSON (paste-ready)

Im Workflow-Editor reicht: HTTP-Request-Node anlegen, Settings setzen wie in Sektion 2. Ein bestehender Login-Node aus den Production-Workflows kann auch dupliziert werden — Login PlentyOne aus 01 Retouren-Erfassung ist die Referenz-Implementation.

12.2 curl (zum Probieren auf der Konsole)

# Token holen — JSON-Body!
TOKEN=$(curl -s -X POST https://p38991.my.plentysystems.com/rest/login \
  -H "Content-Type: application/json" \
  -d '{"username":"n8n-api","password":"PASSWORD"}' \
  | jq -r '.accessToken')

# Beliebigen Call machen
curl -s "https://p38991.my.plentysystems.com/rest/items/12345/images" \
  -H "Authorization: Bearer $TOKEN" | jq

12.3 Python (lokales Skript)

import requests

USER = "n8n-api"
PASSWORD = "..."  # aus Vault, NICHT hardcoden
BASE = "https://p38991.my.plentysystems.com"

# Login — JSON-Body
resp = requests.post(f"{BASE}/rest/login", json={"username": USER, "password": PASSWORD})
resp.raise_for_status()
token = resp.json()["accessToken"]

headers = {"Authorization": f"Bearer {token}"}

# Beispiel: Bilder eines Artikels lesen
imgs = requests.get(f"{BASE}/rest/items/12345/images", headers=headers).json()
print(imgs)

13. Wenn du steckst — Eskalations-Reihenfolge

  1. Response-Body lesen. Plenty gibt bei 4xx-Fehlern in der Regel klare Fehler-JSONs zurück. Erste Anlaufstelle.
  2. Susi fragen — sie kennt das Mokebo-spezifische Setup (Custom-Stati, Mandant, Eigenschaften-IDs). Speziell für Bilder relevant: ob ihr bestimmte Standards habt (Auflösung, Sortierung, Hauptbild-Konvention).
  3. Bestehende Workflows lesenSUB PlentyOne Lookup und 01 Retouren-Erfassung sind die Goldmine. Wenn du ein Pattern brauchst, gucke da zuerst.
  4. PlentyOne Developer Docs: developers.plentymarkets.com
  5. Forum: forum.plentymarkets.com — überraschend aktiv und kompetent.

Letztes Update: 2026-05-06 — Auth-Pattern auf Custom Auth Credential umgestellt (Variables sind Pro-only und auf Starter unbrauchbar). Maintained von Susi (susanna@mokebo.de). Bei Updates: Susi pingen, sie führt das zentral.