Public API für Entwickler
Über diese API kann Ihre CRM-Software oder ein eigenes Skript Angebote in Ihrem Verkäuferkonto anlegen, aktualisieren und mit Fotos versehen – über dieselbe Geschäftslogik, die auch die manuelle Verkäufer-Oberfläche verwendet. Die API ist ausschließlich für Verkäufer gedacht, die bereits ein Konto auf diesem Marketplace haben.
Erste Schritte
- Erstellen Sie einen API-Schlüssel unter Einstellungen → API in Ihrem Verkäuferkonto. Der Schlüssel wird nur einmal vollständig angezeigt.
- Senden Sie ihn als
Authorization: Bearer-Header an/api/public/v1/....
curl -X POST "https://<ihre-domain>/api/public/v1/listings" \
-H "Authorization: Bearer mp_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"newProduct": { "categoryId": "...", "title": "BMW G30 Türgriff links" },
"title": "BMW G30 Türgriff links",
"condition": "used",
"priceCents": 4500,
"vatRate": 19
}'Basis-URL: /api/public/v1 – relativ zu Ihrer Marketplace-Domain, kein separater Host.
Authentifizierung
API-Schlüssel haben das Format mp_live_... (Produktion) bzw. mp_test_... (Test). Sie werden serverseitig nur als HMAC-Hash gespeichert und einmalig bei Erstellung angezeigt.
Beim Erstellen wählen Sie, welche Berechtigungen (Scopes) der Schlüssel erhält:
| Scope | Berechtigt zu |
|---|---|
| listings:read | Angebote lesen, Kategorien/Produkte durchsuchen, Validierung |
| listings:write | Angebote anlegen/ändern, Fotos-Liste, Kompatibilität, Attribute/Bestand nachtragen |
| listings:submit | Angebot zur Prüfung einreichen |
| listings:publish | Angebot direkt veröffentlichen, an der Moderation vorbei (nur wirksam bei admin-freigegebenen Konten, siehe unten) |
| images:write | Fotos hochladen – per multipart oder per URL |
| webhooks:manage | Webhook-Endpunkte über die API anlegen, ändern, testen und löschen |
| keys:self-rotate | Der Schlüssel kann sich selbst vor Ablauf erneuern (optional, für unbeaufsichtigte Integrationen) |
Ein optionales Ablaufdatum können Sie beim Erstellen des Schlüssels selbst festlegen. Die Verwaltung von Schlüsseln (anlegen, rotieren, widerrufen) erfolgt ausschließlich über Ihre eingeloggte Sitzung unter Einstellungen → API, nicht über die Public API selbst – ein kompromittierter Schlüssel kann also nie einen anderen Schlüssel anlegen oder widerrufen.
Angebote (Listings)
Ein „Listing” entspricht genau dem, was die Verkäufer-Oberfläche ein „Angebot” nennt – dieselbe Datenbank, dieselbe Geschäftslogik.
| Methode | Pfad | Scope |
|---|---|---|
| GET | /listings | listings:read |
| GET | /listings/:id | listings:read |
| POST | /listings | listings:write |
| PATCH | /listings/:id | listings:write |
| POST | /listings/:id/photos | images:write |
| POST | /listings/:id/images/from-url | images:write |
| POST | /listings/:id/submit | listings:submit |
| POST | /listings/:id/publish | listings:publish |
| POST | /listings/:id/unpublish | listings:write |
| POST | /listings/:id/archive | listings:write |
| POST | /listings/bulk | listings:write |
| PUT | /listings/upsert | listings:write |
Standardmäßig durchlaufen neue Angebote denselben zweistufigen Prozess wie in der Oberfläche: nach submit geht ein Angebot in die bestehende Moderationswarteschlange, bevor es sichtbar wird.
Für Verkäuferkonten, die ein Marketplace-Admin zusätzlich ausdrücklich als vertrauenswürdig markiert hat, steht mit dem Scope listings:publish ein direkter Weg offen: POST /listings/:id/publish prüft dieselben Voraussetzungen wie submit (mind. ein Foto, alle Pflicht-Attribute der Kategorie, Lagerbestand größer 0) und setzt das Angebot bei Erfolg direkt auf active – ohne Wartezeit auf eine manuelle Prüfung. Ohne diese Freigabe liefert der Endpunkt 403 DIRECT_PUBLISH_NOT_ALLOWED. Der zweistufige Weg über submit bleibt auch für freigegebene Konten weiterhin nutzbar – publish ist eine zusätzliche Option, kein Ersatz. unpublish und archive führen technisch dieselbe Statusänderung aus – der einzige Unterschied ist, welche Aktion Sie aufgerufen haben.
Änderungen per PATCH erfordern einen If-Match-Header mit der aktuellen Version (aus dem ETag einer vorherigen Antwort) – so verhindern wir, dass zwei gleichzeitige Änderungen sich gegenseitig überschreiben.
Ein optionaler Idempotency-Key-Header auf POST-Anfragen sorgt dafür, dass ein wiederholter Request (z. B. nach einem Netzwerkfehler) niemals doppelt anlegt. Für die Synchronisation mit Ihrem CRM können Sie externalId/externalSource mitgeben, oder direkt PUT /listings/upsert verwenden (legt an oder aktualisiert, je nachdem ob der Eintrag schon existiert).
Listen werden per Cursor paginiert, nicht per Seitenzahl:
GET /listings?status=active&limit=50&cursor=eyJjcmVhdGVkQXQiOi...Kategorien & Attribute
Bevor Sie ein Angebot anlegen, können Sie abfragen, welche Felder eine Kategorie erwartet – nützlich, um in Ihrem eigenen System automatisch das passende Formular zu erzeugen.
GET /categories
GET /categories/search?q=bremsbelag&limit=10
GET /categories/{id}
GET /categories/{id}/attributesÜber search lösen Sie einen frei eingegebenen Kategorienamen (auch mit Tippfehlern, dank Trigram-Suche) direkt zu einer categoryId auf, ohne den gesamten Baum selbst durchsuchen zu müssen. Mit leafOnly=true erhalten Sie nur Kategorien, gegen die Sie tatsächlich ein Angebot anlegen können.
Die meisten Kategorien haben kein zusätzliches Attribut-Schema – dort genügen Titel und (vor dem Einreichen) mindestens ein Foto, wie gewohnt. Nur einzelne Kategorien (z. B. Bremsbeläge) verlangen zusätzliche, strukturierte Angaben; diese werden beim Anlegen unter newProduct.attributes mitgeschickt und können später über PATCH /listings/:id/attributes nachgetragen werden. Ein Angebot kann bis zu 100 Attribut-Felder tragen; jeder Wert ist ein einzelner Text, eine Zahl oder ein Wahrheitswert – Listen/Arrays werden aktuell nicht unterstützt.
Produkte (Katalog-Abgleich)
Bevor Sie ein neues Angebot für ein Ersatzteil anlegen, können Sie prüfen, ob dieses Teil – z. B. über die OE-/OEM-Nummer – bereits als Katalogprodukt existiert. So entstehen keine doppelten Katalogeinträge für dasselbe physische Teil, wenn mehrere Verkäufer es anbieten.
GET /products/search?oeNumber=A2129003514
GET /products/search?q=Bremsbelagsatz&categoryId=...oeNumber wird exakt abgeglichen – Schreibweise, Leer- und Trennzeichen spielen dank Normalisierung keine Rolle ("A 212 900 35 14" und "A2129003514" treffen denselben Eintrag). q durchsucht den Titel als Teilzeichenkette. Mindestens eines der beiden Felder ist erforderlich; categoryId/brandId grenzen zusätzlich ein.
Die Treffer sind reine Vorschläge – ob Sie beim Anlegen eines Angebots productId (bestehendes Produkt) oder newProduct (neu anlegen) verwenden, entscheiden ausschließlich Sie – die API verknüpft nichts automatisch.
Bilder
POST /listings/:id/photos
Content-Type: multipart/form-dataErlaubte Formate: JPEG, PNG, WebP. Maximal 8 MB pro Datei, 24 Fotos pro Angebot.
Der tatsächliche Dateiinhalt wird geprüft, nicht nur die angegebene Content-Type – ein falsch deklariertes Format wird abgelehnt.
Liegt das Bild bereits online (eigenes Storage, bestehender CRM-Datensatz), müssen Sie es nicht erst lokal herunterladen, um es hier wieder hochzuladen:
POST /listings/:id/images/from-url
{ "url": "https://ihr-storage.example/teile/12345.jpg" }Nur https://. Die URL wird serverseitig aufgelöst und abgelehnt, wenn sie auf localhost, eine private/interne Adresse oder die Cloud-Metadata-Adresse zeigt (400 UNSAFE_IMAGE_URL); Weiterleitungen (Redirects) werden nicht verfolgt, und die Antwort wird beim Herunterladen laufend gezählt, nicht anhand der vom Server behaupteten Größe (413 IMAGE_DOWNLOAD_TOO_LARGE ab 8 MB). Danach gelten dieselben Prüfungen wie beim direkten Upload (Format, 24-Foto-Limit).
Webhooks
Über Webhooks erfahren Sie in Echtzeit von Änderungen an Ihren Angeboten sowie von neuen Bestellungen und Verkäufen – ohne dass Sie selbst regelmäßig abfragen müssen.
| Ereignis | Ausgelöst durch |
|---|---|
| listing.created / listing.updated | Angebot angelegt bzw. geändert |
| listing.submitted | Angebot zur Prüfung eingereicht |
| listing.approved | Admin hat das Angebot in der Moderation freigegeben |
| listing.published | Angebot ist live (nach Freigabe oder direkter Veröffentlichung) |
| listing.rejected | Admin hat das Angebot abgelehnt |
| listing.archived / listing.unpublished | Angebot deaktiviert – identischer Zustandswechsel, nur der Auslöser unterscheidet sich |
| order.created | Käufer hat eine Bestellung mit Ihrem Artikel aufgegeben (vor Zahlung) |
| offer.sold | Zahlung erfolgreich, Lagerbestand des Angebots auf 0 |
Endpunkte richten Sie entweder über Ihre eingeloggte Sitzung ein (Einstellungen → Webhooks), oder direkt über die API, mit einem Schlüssel, der den Scope webhooks:manage besitzt – nützlich, um die Einrichtung aus Ihrem CRM heraus zu automatisieren. Beide Wege rufen dieselbe Logik auf, es gibt keine zwei getrennten Implementierungen:
| Methode | Pfad |
|---|---|
| GET | /webhooks |
| POST | /webhooks |
| PATCH | /webhooks/:id |
| POST | /webhooks/:id/rotate-secret |
| POST | /webhooks/:id/disable |
| POST | /webhooks/:id/test |
| DELETE | /webhooks/:id |
test schickt ein einzelnes Test-Ereignis nur an den betroffenen Endpunkt, nie an alle Ihre Endpunkte – nützlich, um URL und Secret vor dem Live-Einsatz zu prüfen. Dafür gilt ein deutlich strengeres Rate-Limit als für die übrige Webhook-Verwaltung (siehe unten), da Test-Aufrufe dieselbe Zustellungs-Warteschlange nutzen wie echte Ereignisse.
Jede Zustellung ist per HMAC-SHA256 signiert (X-Marketplace-Signature). Prüfen Sie die Signatur über die exakten, unveränderten Rohdaten des Requests:
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');
// mit dem hex-Teil von X-Marketplace-Signature (Format "v1=<hex>") vergleichenFehlerbehandlung
Jede Fehlerantwort hat dieselbe Struktur:
{
"error": {
"code": "SKU_ALREADY_EXISTS",
"message": "...",
"details": [ { "field": "inventory.sku", "message": "..." } ],
"requestId": "a73c6fbb-ed58-45d7-896d-1a9f3df27e56"
}
}requestId finden Sie auch im X-Request-Id-Response-Header jeder Antwort – geben Sie ihn bei Rückfragen an unseren Support an.
| Status | Bedeutung |
|---|---|
| 400 | Fehlerhafte Anfrage, fehlender If-Match-Header, zu viele Bulk-Items, unsichere Bild-/Webhook-URL |
| 401 | API-Schlüssel fehlt, ungültig, widerrufen oder abgelaufen |
| 403 | Fehlende Berechtigung (Scope), oder Direktveröffentlichung ohne Admin-Freigabe |
| 404 | Ressource existiert nicht oder gehört nicht Ihnen |
| 409 | SKU-/Externe-ID-Konflikt, Idempotency-Key bereits verwendet, ungültiger Angebots-Status für diese Aktion, Lagerbestand leer |
| 412 | If-Match stimmt nicht mit aktueller Version überein |
| 413 | Per URL heruntergeladenes Bild überschreitet 8 MB |
| 415 | Datei-Inhalt entspricht nicht dem angegebenen Format |
| 422 | Fachliche Validierung fehlgeschlagen (z. B. Bild zu groß, Angebot noch nicht bereit zur Veröffentlichung) |
| 429 | Rate-Limit erreicht |
Rate Limits
Begrenzt pro API-Schlüssel und Anfrageart, konfigurierbar serverseitig – Richtwerte:
| Art | Standard-Limit | Für freigegebene Schlüssel |
|---|---|---|
| Lesen (GET) | 300 Anfragen / 60 s | 3000 Anfragen / 60 s |
| Schreiben (POST/PATCH) | 60 Anfragen / 60 s | 600 Anfragen / 60 s |
| Bild-Upload | 30 Anfragen / 60 s | 300 Anfragen / 60 s |
| Bulk-Import | 10 Anfragen / 60 s | 100 Anfragen / 60 s |
| Webhook-Verwaltung | 30 Anfragen / 60 s | — |
| Webhook-Test | 5 Anfragen / 60 s | — |
Bei Überschreitung antwortet die API mit 429 und dem Header Retry-After-publicApiKey.
Für etablierte Integrationen kann ein Marketplace-Admin Ihren Schlüssel auf die höheren Limits in der rechten Spalte hochstufen (Lesen/Schreiben/Bild-Upload/Bulk-Import) – das ist keine Selbstbedienungsfunktion, wenden Sie sich dafür an unseren Support. Die Webhook-Verwaltung/-Test-Limits sind davon unabhängig, da sie eine administrative Aktion und keinen Katalog-/Bestell-Durchsatz begrenzen.
Vollständige Referenz
Alle Endpunkte mit vollständigem Schema finden Sie in der interaktiven API-Dokumentation.
API-Dokumentation öffnen