POST · PUT · Import · Upsert
Import API
Importieren Sie Daten in den Shop. Seit v2.1.0 mit RESTful HTTP-Methoden-Trennung: POST für neue Datensätze, PUT für Updates.
POST /Import/JSON/{entity} # INSERT
PUT /Import/JSON/{entity} # UPDATE
POST = INSERT
Neue Datensätze anlegen
- Erstellt nur neue Datensätze
- ID = 0 oder keine ID → automatische ID-Vergabe
- ID > 0 die bereits existiert →
409 Conflict
- Beispiel: Neue Kategorie anlegen
POST /Import/JSON/categories
{"data": [{"categories_id": 0, ...}]}
PUT = UPDATE
Existierende Datensätze aktualisieren
- Aktualisiert nur existierende Datensätze
- ID muss existieren, sonst →
404 Not Found
- Keine neue Datensätze möglich
- Beispiel: Kategorie-Beschreibung ändern
PUT /Import/JSON/categories
{"data": [{"categories_id": 142, ...}]}
Migration von v2.0.x: Das bisherige POST-Verhalten (Upsert) wird nicht mehr unterstützt. Trennen Sie Ihre Imports in INSERT (POST) und UPDATE (PUT) auf.
| Entity |
POST (INSERT) |
PUT (UPDATE) |
Scope |
| Produkte |
POST /Import/JSON/products |
PUT /Import/JSON/products |
products:write |
| Kategorien |
POST /Import/JSON/categories |
PUT /Import/JSON/categories |
categories:write |
| Kunden |
POST /Import/JSON/customers |
PUT /Import/JSON/customers |
customers:write |
| Hersteller |
POST /Import/JSON/manufacturers |
PUT /Import/JSON/manufacturers |
manufacturers:write |
| News-Artikel |
POST /Import/JSON/newsdesks |
PUT /Import/JSON/newsdesks |
news:write |
| News-Kategorien |
POST /Import/JSON/newsdeskcats |
PUT /Import/JSON/newsdeskcats |
newscategories:write |
| FAQ |
POST /Import/JSON/faq |
PUT /Import/JSON/faq |
faq:write |
| FAQ-Kategorien |
POST /Import/JSON/faqcats |
PUT /Import/JSON/faqcats |
faqcategories:write |
| SEO History |
POST /Import/JSON/seohistory |
PUT /Import/JSON/seohistory |
seohistory:write |
| Contracts |
POST /Import/JSON/contracts |
PUT /Import/JSON/contracts |
contracts:write |
| Newsletter Subscribers |
POST /Import/JSON/newsletter_subscribers |
PUT /Import/JSON/newsletter_subscribers |
newsletter:write |
| Newsletter Campaigns |
POST /Import/JSON/newsletters |
PUT /Import/JSON/newsletters |
newsletters:write |
| Tickets v2.60.0 |
POST /Import/JSON/tickets |
PUT /Import/JSON/tickets |
tickets:write |
| Bestellungen NEU v2.20.0 |
POST /Import/JSON/orders |
PUT /Import/JSON/orders |
orders:write |
| Media NEU v2.16.0 | POST /Import/JSON/media | PUT /Import/JSON/media/{id} | media:write |
| Slider NEU v2.22.0 |
POST /Import/JSON/slider |
PUT /Import/JSON/slider |
slider:write |
| Projekte (xoCRM) NEU v2.35.0 |
POST /Import/JSON/projects |
PUT /Import/JSON/projects |
projects:write |
| Projektaufgaben NEU v2.35.0 |
POST /Import/JSON/project_tasks |
PUT /Import/JSON/project_tasks |
project_tasks:write |
| Termine (xoCRM) NEU v2.44.0 |
POST /Import/JSON/appointments |
PUT /Import/JSON/appointments |
appointments:write |
| Merkmale & Optionswerte NEU v2.96.0 |
POST /Import/JSON/products_options |
PUT /Import/JSON/products_options |
products:write |
| Lieferzeit-Profile NEU v2.94.0 |
POST /Import/JSON/shipping_profiles |
PUT /Import/JSON/shipping_profiles |
products:write |
| Ticket-Abteilungen NEU v2.103.0 |
POST /Import/JSON/ticket_departments |
PUT /Import/JSON/ticket_departments |
tickets:write |
| Einstellungen NEU v2.108.0 |
– |
PUT /Import/JSON/configuration |
configuration:write |
| CSV-/XLS-Porter NEU v2.109.0 |
– |
PUT /Import/JSON/porters |
porters:write |
| Ticket-Claims NEU v2.118.0 |
– |
PUT /Import/JSON/ticket_claims |
tickets:write |
Neu in v2.57.0: POST/PUT /Import/JSON/customers pflegt jetzt Kundenrabatte über das Feld discounts[] (Prozentrabatt je Kategorie; category_id: 0 zusammen mit Unterkategorien = gesamtes Sortiment; optionale Flags subcategories, qpb, option, special). Semantik „replace-on-present“: Ist das Feld vorhanden, wird der komplette Rabatt-Bestand des Kunden ersetzt; ein leeres Array [] entfernt alle Rabatte; fehlt das Feld, bleiben die Rabatte unangetastet. Zusätzlich sind customers_id_extern (WaWi-Kundennummer) und customers_group_id (wird gegen die vorhandenen Kundengruppen validiert) schreibbar, und PUT kann den Kunden auch ohne E-Mail über customers_id oder customers_id_extern adressieren (reine Rabatt-/Stammdatenpflege) – POST verlangt weiterhin E-Mail + Adresse.
Neu in v2.57.0: POST/PUT /Import/JSON/products schreibt kundenindividuelle Preise über das neue Feld customers_prices[]: Kunde per customers_id oder customers_id_extern (WaWi-Kundennummer), Partial Upsert (nur mitgesendete Spalten werden geschrieben, z. B. customers_price, Staffelpreise products_price1..12 + products_price1..12_qty, products_qty_blocks, products_min_qty, products_max_qty), Löschen per "_action": "delete", Vollspiegel per customers_prices_replace: true (zusammen mit leerem customers_prices: [] werden alle Kundenpreise des Produkts entfernt). Der Products-Export liefert customers_prices Round-Trip-fähig.
Neu in v2.24.0: PUT /Import/JSON/tickets kann jetzt nicht nur interne Kommentare, sondern auch öffentliche Antworten an Kunden absenden. Pro Item zwei optionale boolean-Flags: is_public_reply: true macht den History-Eintrag öffentlich (im Frontend sichtbar), notify_customer: true verschickt zusätzlich eine E-Mail an den Kunden über das $mail_ticket_update_with_content-Template (analog Backend-Antwort-Pfad). Beide Flags müssen strikt boolean true sein — alle anderen Werte werden ignoriert. Ab v2.25.0: bei is_public_reply: true wird automatisch die im Backend hinterlegte Admin-Signatur angehängt (Verlaufseintrag + E-Mail) — Opt-out via append_signature: false. In ticket_comments daher keine eigene Signatur mehr mitschicken. admin_id > 0 ist bei öffentlichen Antworten Pflicht. Default-Verhalten (ohne Flags) bleibt intern + ohne Mail — backward compatible mit allen v2.11.0+ Clients. Ab v2.60.0: POST/PUT /Import/JSON/tickets nimmt zusätzlich attachments[] entgegen (name + content_base64) — Ticket-Anhänge sind damit nicht mehr nur lesbar, sondern auch schreibbar. Die Dateien hängen am Verlaufseintrag: ohne ticket_comments im selben Request werden sie mit einer Fehlermeldung abgelehnt.
Neu in v2.20.0: POST/PUT /Import/JSON/orders ermöglicht das Anlegen und Aktualisieren von Bestellungen. Delegiert an den generischen Service \\Xonic\\Order\\OrderBuilder, der auch von Marketplace-Importern wiederverwendet werden kann. Lagerbestand wird via xo_stock_change gebucht (Best-Effort).
Neu in v2.17.0: PUT /Import/JSON/products kann produktbezogene Sonderangebote über specials[] pflegen: mehrere Kundengruppen, Netto-Sonderpreis oder discount_percent, Zeitraum, Status, Angebotsbezeichnung und Löschung pro Gruppe.
{
"type": "categories",
"data": [
{
"categories_id": 142,
"parent_id": 110,
"status": 1,
"languages": {
"de": {
"categories_name": "Produktattribute",
"categories_description": "<p>Beschreibung als HTML-String...</p>"
}
}
}
]
}
{
"success": true,
"api_version": "2.1.0",
"type": "categories",
"stats": {
"total": 2,
"inserted": 1,
"updated": 1,
"failed": 0
},
"errors": [],
"timestamp": "2026-01-27 14:00:00",
"records": [
{"index": 1, "categories_id": 144, "status": "inserted"},
{"index": 2, "categories_id": 142, "status": "updated"}
]
}
409 Conflict (POST)
ID existiert bereits:
{"success":false,"error":{"code":"ALREADY_EXISTS"}}
404 Not Found (PUT)
ID existiert nicht:
{"success":false,"error":{"code":"NOT_FOUND"}}
Zusatzfelder eines Produkts werden ausschließlich über das Feld extra_fields geschrieben – nicht als Feld auf oberster Ebene des Produkts. Ein Schlüssel wie google_product_category direkt neben products_model wird ignoriert (und in der Response als UNKNOWN_FIELD gemeldet).
Ab v2.51.0 wird jede der folgenden Schreibweisen akzeptiert – wahlweise über die Feld-ID oder den Feldnamen (Groß-/Kleinschreibung und Leerzeichen sind tolerant):
{
"type": "products",
"data": [{
"products_model": "ABC-123",
"extra_fields": {
"Google Produktkategorie (xoPort)": "1234",
"Google Zustand (xoPort)": "new",
"Google Verfügbarkeit (xoPort)": "in stock"
}
// ... alternativ nach Feld-ID:
// "extra_fields": {"7": "1234", "8": "new", "9": "in stock"}
// ... oder als Liste:
// "extra_fields": [{"products_extra_fields_id": 7, "products_extra_fields_value": "1234"}]
}]
}
Auch die Struktur, die der JSON-Export zurückliefert (Objekt, gekeyt nach Feld-ID), ist unverändert wieder importierbar.
Google-Shopping-Felder
Die Felder des Google-Feeds sind Standard-Zusatzfelder mit festen IDs:
| Feld-ID |
Feldname (Standard) |
Google-Feed |
7 |
Google Produktkategorie (xoPort) |
g:google_product_category |
8 |
Google Zustand (xoPort) |
g:condition |
9 |
Google Verfügbarkeit (xoPort) |
g:availability |
Hinweis: Ein leerer String "" leert das Feld, null bedeutet „nicht mitgeliefert“ (das Feld bleibt unverändert). Ein Feldname, den es im Shop nicht gibt, wird als EXTRA_FIELD-Warnung in der Response gemeldet statt stillschweigend verworfen.
Produkt-Tabs (z. B. „Leistungsbeschreibung“, „Datenblatt“, „Montagehinweise“) werden über das Feld tabs gepflegt – ein flaches Array von Tab-Objekten. Ein sprachgeschachteltes Format wie {"de": […]} wird nicht unterstützt und ohne Fehlermeldung ignoriert (keine Warnung, keine Tabs).
{
"type": "products",
"data": [{
"products_model": "ABC-123",
"tabs": [
{
"tab_name": "Leistungsbeschreibung",
"tab_content": "<h3>Leistungsbeschreibung</h3><p>...</p>",
"language_id": 2,
"sort_order": 0
},
{
"tab_name": "Service description",
"tab_content": "<h3>Service description</h3><p>...</p>",
"language_id": 1,
"sort_order": 0
}
]
}]
}
| Feld |
Pflicht |
Beschreibung |
tab_name |
✓ (bei Neuanlage) |
Titel des Tabs, wie er am Produkt angezeigt wird. Ohne tab_name wird der Eintrag übersprungen. |
tab_content |
– |
HTML-Inhalt des Tabs. |
language_id |
– |
Sprache des Eintrags (1 = Englisch, 2 = Deutsch). Ohne Angabe greift die Standardsprache des Shops. |
sort_order |
– |
Sortierung des Tabs am Produkt (Default 0). |
tab_id |
– |
Nur für Updates: aktualisiert tab_name/tab_content eines bestehenden Tabs in der angegebenen Sprache. |
Verhalten im Detail
- Neuanlage: Jeder Eintrag ohne
tab_id legt einen neuen Tab mit eigener tab_id an – auch pro Sprache. Mehrsprachige Tabs werden also als ein Eintrag je Sprache gesendet (siehe Beispiel oben); das Frontend zeigt sprachgefiltert jeweils nur die Tabs der aktiven Sprache.
- Update: Mit
tab_id wird der bestehende Tab in der angegebenen Sprache aktualisiert – allerdings nur, wenn die Sprachzeile bereits existiert. Eine fehlende Sprachfassung eines bestehenden Tabs kann per API nicht nachträglich angelegt werden (dafür einen neuen Tab ohne tab_id senden).
- Kein Delete-on-absent: Nicht mitgesendete Tabs bleiben unverändert bestehen. Tabs löschen erfolgt im Backend am Produkt.
- Export-Round-Trip:
GET /products liefert die Tabs als Objekt gekeyt nach tab_id (inkl. tab_name, tab_content, language_id, sort_order).
Achtung: Das Feld heißt tab_name/tab_content – nicht tabs_title/tabs_content. Einträge in falscher Schreibweise oder in sprachgeschachtelter Struktur werden kommentarlos verworfen.
Bestand, Sicherheitsbestand und Meldebestand je Lager werden im Produkt-Import ausschließlich über das Feld storages gepflegt – ein flaches Array von Lager-Objekten, jedes mit storages_id (Alias warehouse_id). Seit v2.45.0 ist das ein partielles Update: ein Teil-Payload wie {"storages_id": 3, "products_reorder_level": 10} ändert nur den Meldebestand dieses Lagers und setzt die übrigen Lagerspalten nicht auf 0.
Voraussetzung ist das aktivierte erweiterte Lagersystem (EXTENDED_STORAGE_SYSTEM); andernfalls wird das Feld ignoriert und die Antwort enthält die Warnung STORAGE_SYSTEM_DISABLED. Alle Felder, die Semantik und die Fallstricke stehen im Abschnitt Lagerbestände & Meldebestände je Lager der Products-Referenz.
Achtung: products_reorder_level und products_safe_quantity auf oberster Ebene des Produkts betreffen das Hauptlager (die products-Tabelle), nicht ein Lager aus storages – und sind erst ab v2.63.0 importierbar; davor wurden sie als unbekanntes Feld verworfen. storages_id: 1 ist bereits das erste Zusatzlager, nicht das Hauptlager. Ein Lager-Eintrag ohne storages_id wird ohne Warnung übersprungen – der Import meldet dann Erfolg, obwohl nichts geschrieben wurde.
Import starten
Erstellen Sie einen OAuth2 Client mit ":write" Scopes für die gewünschten Ressourcen.
OAuth2 Client erstellen