Zum Hauptinhalt springen

Übersicht

Stapelimporte laufen asynchron: Der POST legt einen Import-Job an und antwortet sofort mit 202 und einer jobId. Ihr System fragt anschließend den Job-Status ab (GET /api/import-jobs/{jobId}), bis der Import abgeschlossen ist, und erhält dort das vollständige Ergebnis — inklusive der Gründe für übersprungene oder fehlgeschlagene Zeilen. Der Import nutzt dieselbe Engine wie der CSV-Import in der App: gleiche Validierung, gleiches idempotentes Upsert, gleiches Ergebnisformat.
EndpunktVorgangUpsert-Schlüssel
POST /api/articlesArtikel im Stapel importiereninternalArticleNr
POST /api/suppliersLieferanten im Stapel importierenexternalSupplierId bzw. Name
POST /api/ordersBestellungen im Stapel importierenorderNumber
GET /api/import-jobs/{jobId}Job-Status und Ergebnis abfragen
Ein POST mit einem einzelnen Objekt (ohne rows-Array) bleibt synchron und antwortet unverändert mit 201. Nur die Stapel-Form (Array bzw. Envelope) ist asynchron.

Ablauf

  1. Stapel sendenPOST mit einem Array von Zeilen oder einem Envelope-Objekt:
    { "rows": [ { "…": "…" } ] }
    
    Bei Erfolg: 202 mit { "jobId": "…", "status": "queued", "statusUrl": "/api/import-jobs/…" } (auch als Location-Header). Zu diesem Zeitpunkt ist noch nichts importiert. Die Zeilen-Validierung läuft synchron — ein fehlerhafter Payload wird sofort mit 400 abgelehnt, ohne dass ein Job entsteht.
  2. Status abfragenGET /api/import-jobs/{jobId} etwa alle 2 Sekunden, bei großen Jobs mit steigendem Abstand (z. B. exponentiell bis ~30 s). completed und failed sind final — danach nicht weiter abfragen.
  3. Ergebnis auswerten — bei status: "completed" enthält result das zusammengeführte Import-Ergebnis (imported, updated, failed, skipped, … je nach Entität). Fehlgeschlagene Zeilen tragen stabile, nicht lokalisierte Fehlerschlüssel.

Limits

  • Maximal 5.000 Zeilen pro Anfrage. Darüber: 413 { "error": "batchTooLarge", "maxBatchSize": 5000 } — es wird nichts angelegt. Größere Abgleiche auf mehrere Anfragen aufteilen.
  • ~4,5 MB Request-Body (Plattform-Limit, greift vor unserer Anwendung).
  • Ein leeres Array oder ein fehlerhaftes Envelope → 400.

Teil-Akzeptanz und Idempotenz

  • Gültige Zeilen werden übernommen, auch wenn andere Zeilen in failed landen.
  • Ein identischer Import lässt sich gefahrlos wiederholen: bereits vorhandene Zeilen kommen als übersprungen/aktualisiert zurück, es entstehen keine Duplikate.

Bestellungen: replaceLines und die Vollständigkeitsregel

Der Import einer Bestellung ersetzt deren komplette Positionsliste. Eine Anfrage, die Bestellung O enthält, muss deshalb alle Positionen von O enthalten. Werden die Positionen einer Bestellung über mehrere Anfragen verteilt, würde die zweite Anfrage die zuvor importierten Positionen löschen. Die Positions-Schutzsperre macht daraus einen lauten Fehler statt eines stillen Datenverlusts:
  • Standard (replaceLines fehlt oder false): Würde der Stapel gespeicherte Positionen löschen, schlägt der gesamte Job vor dem ersten Schreibvorgang fehl. Im error-Feld des Jobs steht:
    { "key": "lineReplacementRequired", "orderNumbers": ["O"] }
    
    Senden Sie die betroffenen Bestellungen mit ihrer vollständigen Positionsliste erneut.
  • "replaceLines": true autorisiert das Ersetzen: Die gesendete Positionsliste wird maßgeblich, auch wenn dabei Positionen entfallen.

Fehlerschlüssel (stabil, nicht lokalisiert)

SchlüsselBedeutung
batchTooLargeAnfrage überschreitet das 5.000-Zeilen-Limit (HTTP 413)
lineReplacementRequiredEin Bestell-Job würde gespeicherte Positionen löschen — vollständige Positionslisten senden oder replaceLines setzen
dispatchFailedJob wurde angelegt, aber die Verarbeitung konnte nicht gestartet werden (HTTP 500); es läuft keine Arbeit
invalidPayloadDie Zeilen sind bei der erneuten Prüfung im Worker durchgefallen; error enthält eine JSON-Zusammenfassung (path + code, nie die Werte selbst)
workerErrorUnerwarteter Fehler nach allen Wiederholungsversuchen (Details serverseitig erfasst)
dbErrorPro Zeile: unerwarteter Datenbankfehler ohne spezifischen Schlüssel

Aufbewahrung

Das Job-Ergebnis bleibt abfragbar; der gestagte Eingabe-Payload wird ~24 Stunden nach Abschluss freigegeben, der Job-Datensatz ~30 Tage nach Erstellung gelöscht. Fragen Sie das Ergebnis innerhalb dieses Fensters ab und persistieren Sie es auf Ihrer Seite.

Berechtigungen

Der POST erfordert den Write-Scope der Entität (articles:write, suppliers:write, orders:write), das Abfragen des Jobs den jeweiligen Read-Scope (im Write-Scope enthalten). Jobs sind strikt mandantengebunden: eine unbekannte jobId und die jobId eines anderen Unternehmens liefern dieselbe 404.