Import-API: Daten per POST & PUT importieren

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

HTTP-Methoden (v2.1.0)

RESTful Semantik für klare Absicht:

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.

Verfügbare Endpoints

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.0POST /Import/JSON/mediaPUT /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.

Request-Format

Der Import erwartet ein data-Array mit den zu importierenden Objekten:

{
  "type": "categories",
  "data": [
    {
      "categories_id": 142,
      "parent_id": 110,
      "status": 1,
      "languages": {
        "de": {
          "categories_name": "Produktattribute",
          "categories_description": "<p>Beschreibung als HTML-String...</p>"
        }
      }
    }
  ]
}

Response-Format (v2.1.0)

Die Response enthält ein records-Array mit dem Status jedes Datensatzes:

{
  "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"}
  ]
}

Fehler-Responses

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"}}

Produkt-Zusatzfelder (extra_fields)

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 (tabs)

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 je Lager (storages)

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

Datenexport mit der xoPort API

Die Export-Endpoints der xoPort API ermöglichen den lesenden Zugriff auf alle relevanten Shop-Daten. Ob für die Synchronisation mit ERP-Systemen, das Erstellen von Backups oder für Business-Intelligence-Analysen – die Export-API liefert konsistente, strukturierte JSON-Daten.

Performance-Optimierung

  • Nutzen Sie Einzel-Exports (/product/{id}) für gezielte Abfragen
  • Verwenden Sie Filter-Parameter um die Datenmenge zu reduzieren
  • Implementieren Sie Caching auf Client-Seite
  • Für große Datenmengen: Inkrementelle Synchronisation basierend auf last_modified