Übersicht
Die PPWR-API ist die vollständige Maschinenschnittstelle für die EPR-Mengenmeldung. Sie besteht aus diesen Gruppen, die aufeinander aufbauen:
Der Stammdaten-Intake läuft über dieselbe Upsert-Engine wie der CSV-/Excel-Import in der App:
gleiche Zeilenschemata, gleiche Validierung, gleicher Schreibpfad, gleiche Idempotenz, gleiche
Spaltenpolitik. Ein Unterschied: Die API übersetzt keine Werte — siehe
Anfrageformat.
Berechtigungen
Schlüsselmechanismus, Basis-URL und die allgemeinen Statuscodes stehen in der Einführung. Für PPWR gelten zwei Berechtigungen:
Die Schreibberechtigung schließt die Leseberechtigung ein — ein Schlüssel mit
packaging:write deckt
das gesamte Integrationsrezept weiter unten ab.
Modul- und Mandantengrenze. Alle Endpunkte setzen einen Kundenarbeitsbereich voraus, und das
PPWR-Modul muss für den Benutzer aktiviert sein, für den der Schlüssel handelt (persönlicher
Schlüssel: sein Inhaber; Unternehmensschlüssel: der ranghöchste aktive Benutzer des
Arbeitsbereichs). Sonst lautet die Antwort
403 (im Kundenarbeitsbereich:
PPWR compliance module is not enabled). Jeder Schlüssel ist an genau ein Unternehmen gebunden; er
kann Daten eines anderen Unternehmens weder lesen noch schreiben.Stammdaten-Intake
Endpunkte und Abgleichschlüssel
Alle fünf Endpunkte antworten synchron mit dem Ergebnis im Body — anders als
POST /api/v1/ppwr/quantities, das einen asynchronen Job anlegt. Ein Einzelobjekt ohne rows ist 400.
Anfrageformat
Jeder dieser Endpunkte nimmt entweder ein bloßes Array von Zeilen oder ein Objekt mitrows:
plastic, sales, manufacturer, …). Lokalisierte
Bezeichnungen wie Kunststoff und die gespeicherten Wertzuordnungen Ihres Arbeitsbereichs wirken nur
im Datei-Import; über die API scheitert ein solcher Wert mit invalidPpwrValue. Auf der Zeile selbst
sind 12 und "12", ["plastic"] und "plastic;paper_board", true und "ja" jeweils
gleichwertig. Innerhalb von sizes[] gilt das nicht: gtin und reference müssen JSON-Strings sein
("4012345678918", keine Zahl), sonst landet die ganze Zeile in invalid.
overwrite wirkt nur bei Komponenten, Verpackungseinheiten und Größen (formats). Die
Stücklisten- und Artikelverknüpfungs-Endpunkte akzeptieren das Feld und ignorieren es. Was sie stattdessen tun, steht
unter Größen und Verknüpfungen: keine Bestätigung.Spaltenpolitik: standardmäßig nur befüllen
- Eine leere oder weggelassene optionale Zelle ändert nichts — sie löscht nie einen gespeicherten Wert.
-
Pflichtspalten sind auf jeder Zeile nötig, auch auf einer Zeile, die nur einen bestehenden
Datensatz aktualisiert: auf Einheiten-Zeilen
name,packagingType,roleundnotes, auf Komponenten-Zeilencode,nameundmaterials. Eine Zeile ohne sie landet ininvalid. Ihre Werte werden wie jede andere Zelle mit den gespeicherten verglichen: Ein abweichendernameaus Ihrem ERP ist eine Überschreibung. - Eine Zelle, die ein bisher leeres Feld befüllt, wird automatisch übernommen.
-
Eine Zelle, die einen vorhandenen Wert überschreiben würde (auch lieferanten- oder
KI-attestierte regulatorische Werte), verlangt
overwrite: true. Ohne dieses Flag wird ein Stapel, der eine solche Änderung enthält, mit409abgelehnt und es wird nichts geschrieben:Senden Sie den Stapel erneut mitoverwrite: trueoder entfernen Sie die betroffenen Zeilen. -
Ein ausdrückliches „nein” schaltet ein Ja/Nein-Merkmal aus. Bei
contactSensitiveundfoodContact(Komponenten) sowieplacedUnderOwnBrand,modifiesConformityundreusable(Verpackungseinheiten) behält eine leere Zelle den gespeicherten Wert;false(oder"no") auf einem gespeichertentrueist eine Überschreibung und verlangtoverwrite: true. -
Feldübergreifende Regeln prüfen die wirksamen Merkmale. Nennt eine Zeile ein Ja/Nein-Merkmal
oder einen PFAS-Wert, zählt die Zelle der Zeile und, wo sie leer ist, der gespeicherte Wert. Eine
Zeile mit PFAS-Werten für eine Komponente, die bereits als Lebensmittelkontakt gespeichert ist, wird
angenommen. Schaltet eine Zeile ein gespeichertes
foodContactaus, werden die gespeicherten PFAS-Daten mitgeprüft:foodContactOffWithPfasData, oderpfasOnlyFoodContact, wenn die Zeile selbst PFAS-Werte enthält. - Herkunfts- und Dokumentspalten (zuletzt liefernder Lieferant, Zeitstempel, Verweise auf Dokumente) sind über diese API niemals beschreibbar.
Lieferanten
supplierName auf einer Komponenten- oder Einheiten-Zeile wird unscharf mit Ihren Lieferanten
abgeglichen: Groß-/Kleinschreibung, Umlaute (ä = ae), Satzzeichen und Rechtsformen wie GmbH oder
AG werden ignoriert, Verso trifft also Verso AG.
- Beim Anlegen erzeugt ein Name, der keinen Ihrer Lieferanten trifft, einen neuen Lieferanten
mit diesem Namen (ohne E-Mail, ohne Land, als PPWR-relevant
markiert), und der neue Datensatz wird ihm zugeordnet. Das Ergebnis nennt die neuen Namen in
suppliersCreated. Prüfen Sie die Schreibweise vor dem Senden: Ein Tippfehler legt einen zweiten Lieferanten an. - Ein Name, der zwei oder mehr Ihrer Lieferanten trifft, lässt die Zeile mit
ambiguousSupplierNamescheitern. - Bei einer bestehenden Komponente oder Verpackungseinheit wird
supplierNameignoriert — der Lieferant wird beim Anlegen festgelegt und vom Intake nie geändert. - Komponenten werden über
codeim Katalog des genannten Lieferanten gesucht, sonst in Ihrem eigenen. Innerhalb einer Anfrage werden sie allein nachcodezusammengefasst: Zwei Zeilen mit demselbencode, aber verschiedenen Lieferanten, sind ein Schlüssel, und die zweite scheitert mitduplicateInFile. - Eine Zeile, deren
supplierNamenoch keinen Lieferanten trifft, für einencode, den es bisher nur bei einem anderen Lieferanten gibt, scheitert mitambiguousComponentMatch— und für sie wird kein Lieferant angelegt.
Einheiten: die Referenz-Falle
notes ist auf Einheiten-Zeilen Pflicht — es ist die „Beschreibung & Verwendungszweck”, die auch
das Formular verlangt. Eine Zeile ohne nicht-leeres notes scheitert mit descriptionRequired, auch
wenn sie nur eine bestehende Verpackungseinheit aktualisiert.
Eine Einheiten-Zeile kann ihre Komponenten in componentCodes nennen (ein Code ohne Komponente legt
eine Platzhalter-Komponente an) oder, bei einer einteiligen Verpackung, material und optional
massGrams tragen: Die Verpackungseinheit erhält dann eine Hauptteil-Komponente aus diesem Material —
eine bestehende Verpackungseinheit nur, solange sie keine Komponente hat. material zusammen mit
componentCodes scheitert mit materialWithComponentCodes, massGrams ohne material mit
materialsRequired. componentCodes und articleNumbers fügen immer nur Verknüpfungen hinzu; sie
entfernen nie eine.
Größen einer Einheit
Die eigenen Maße und diegtin der Einheiten-Zeile sind die erste (primäre) Größe. Weitere
Größen senden Sie im optionalen Array sizes derselben Zeile — das ist der vorgesehene Weg:
- Beim Anlegen bricht eine Größe, deren
referenceschon von einer anderen Verpackungseinheit oder Größe belegt ist, oder die die Einheit über 50 Größen bringen würde, die ganze Zeile ab: Die Verpackungseinheit entsteht nicht, und die Zeile scheitert mitduplicateFormatReferencebzw.formatCapReached. Eine Größe, die schon innerhalb der Anfrage abgewiesen wird — dieselbereferencezweimal in einer Einheit (duplicateFormatReference) oder einconflictingSizeRow—, scheitert allein, und die Verpackungseinheit wird ohne sie angelegt. - Trifft die Zeile eine bestehende Verpackungseinheit, wird jede Größe für sich aktualisiert; eine
abgewiesene Größe scheitert allein. Größen, die die Anfrage nicht nennt, werden nie gelöscht.
Ein hier geänderter Größenwert zählt als Überschreibung und verlangt
overwrite: true. - Dieselbe Gruppierung geht auch flach: mehrere Zeilen mit derselben
internalRef, jede mit eigenersizeReference. So deklariert das CSV-Blatt seine Größen — eine Zelle kann kein Array tragen. Beide Schreibweisen dürfen sich in einer Anfrage mischen. POST /api/v1/ppwr/formatsschreibt Größen als eigener Endpunkt, für Aufrufer, die Größen bereits getrennt versenden (siehe unten).
Größen und Verknüpfungen: keine Bestätigung
POST /api/v1/ppwr/formats folgt der Spaltenregel von Verpackungseinheiten und Komponenten: Eine
leere Zelle behält den gespeicherten Wert, und ein nicht-leerer Wert, der einen gespeicherten ändern
würde, wird mit 409 { "error": "overwriteRequired", "confirmUpdateCount": … } abgelehnt — es wird
nichts geschrieben —, bis Sie den Stapel mit overwrite: true erneut senden. Eine Zeile, deren
reference die eigene internalRef der Verpackungseinheit ist, adressiert die primäre Größe —
also GTIN und Maße der Einheit selbst.
POST /api/v1/ppwr/linkslegt eine Verknüpfung zwischen Verpackungseinheit und Komponente an. Eine bestehende Verknüpfung ändert er nie: Eine abweichendequantityoder einmassGramsohneformatReferenceauf einer schon vorhandenen Verknüpfung wird ignoriert, und die Zeile kommt alsskippedzurück. Ändern Sie diese Werte in der App.- Eine
links-Zeile mitformatReferencesetzt die Masse der Komponente für diese Größe immer auf dasmassGramsder Zeile, auch wenn die Verknüpfung schon besteht (die Zeile kommt trotzdem alsskippedzurück). Ein leeresmassGramsbehält dort die gespeicherte Masse dieser Größe. POST /api/v1/ppwr/article-linkslegt eine Verknüpfung zwischen Verpackungseinheit und Artikel an; eine bestehende istskipped.
Limits
Höchstens 1.000 Zeilen pro Anfrage (400). Nicht offensichtlich: Eine Einheiten-Anfrage trägt
zusätzlich höchstens 1.000 sizes-Einträge über alle Zeilen — eine Zeile darf bis zu 49 Größen
anfordern (eine Verpackungseinheit trägt höchstens 50, die primäre eingerechnet), das Zeilenlimit
allein begrenzt den Schreibvorgang also nicht. Über einem der beiden Limits lautet die Antwort 400
mit Too many rows: max 1000 per request bzw. Too many sizes: max 1000 per request, und es wird
nichts geschrieben. Ein Limit je Schlüssel gibt es nicht, es gilt aber das allgemeine
Rate-Limit von 1000 Anfragen pro Minute je
IP-Adresse.
Ergebnis
invalidenthält Zeilen, die das Zeilenschema abgelehnt hat, bevor etwas geschrieben wurde;indexist die Position der Zeile in der Anfrage (ab0).failedenthält Zeilen, die die Engine abgelehnt hat.- Teil-Akzeptanz: Gültige Zeilen werden übernommen, auch wenn andere in
invalidoderfailedlanden. Einzige Ausnahme ist die409-Überschreibungssperre — die gilt für den ganzen Stapel. - Idempotenz: Eine identische Anfrage erneut zu senden ändert nichts; getroffene Zeilen kommen
als
skippedzurück. Ausnahme sind Einheiten-Zeilen ohneinternalRef(siehe oben). failedenthält auch neue Verpackungseinheiten, die abgelehnt wurden, weil das Verpackungseinheiten-Kontingent Ihres Tarifs erreicht ist, mitpackagingUnitQuotaReached(ihre Größen ebenso). Eine abgelehnte Einheit legt nichts an, auch nicht ihren Lieferanten.
Bei
units steckt eine Größe, die zusammen mit einer neuen Verpackungseinheit angelegt wird, in deren
imported; eine Größe, die an einer bestehenden Verpackungseinheit angelegt wird, erscheint in
updated als <Einheit> / <Größe>.
Fehlerschlüssel
Die Werte in
failed[] sind stabile, nicht lokalisierte Maschinenschlüssel — alles
Unerwartete wird auf dbError reduziert. In invalid[] ist das meistens ebenfalls ein Schlüssel,
aber ein Feld ohne eigene Meldung (etwa ein Text über 120 Zeichen oder ein Eintrag in sizes[])
fällt auf den englischen Standardtext des Validators zurück. Werten Sie deshalb auf bekannte
Schlüssel aus und zeigen Sie den Rest unverändert an.invalid[]:
Engine-Schlüssel stehen in
failed[]:
Stammdaten zurücklesen
Alles, was der Intake schreibt, lässt sich auch wieder lesen — mitpackaging:read (ein Schlüssel
mit packaging:write deckt das mit ab).
- Der Weg vom Schlüssel zur ID.
?internalRef=U-1bzw.?code=K-1löst den Abgleichschlüssel des Intakes auf die Zeile auf — so kommen Sie an dieid, die die Detail-Endpunkte erwarten. Ein leerer Wert ist kein Filter, der nichts trifft, sondern gar kein Filter. codetrifft eine Menge, keine Zeile. Ein Komponenten-Code ist je Lieferant eindeutig: Derselbe Code kann einmal in Ihrem eigenen Katalog und einmal bei einem antwortenden Lieferanten liegen.?supplierId=<uuid>grenzt darauf ein,?supplierId=noneauf Ihren eigenen Katalog. Ein anderer Wert trifft nichts, statt die Menge zu erweitern.- Paging ist begrenzt. Ohne
limitkommen die ersten 100 Zeilen (limitmax. 1000); blättern Sie mitoffset, bis eine Seite kürzer ist. Die Sortierung ist stabil (Verpackungseinheiten nach Name, Komponenten natürlich nach Code, jeweils mit deridals Gleichstand-Kriterium), Seitengrenzen wiederholen oder überspringen also keine Zeile. Ein ungültiger Wert ist ein400. - Gelöschtes bleibt draußen, und eine fremde, unbekannte oder syntaktisch falsche ID ist immer
404— nie403, das würde die Existenz einer fremden ID bestätigen.
Dokumente hochladen
Ein Dokument — Prüfbericht, Konformitätserklärung, technische Zeichnung — lässt sich per API an eine Verpackungseinheit oder eine Komponente hängen. Das ist das API-Gegenstück zu Nachweis hochladen in der App und läuft durch denselben Code: Das Dokument wird mit dem gesendeten Typ als genehmigt abgelegt, identische Bytes werden als Duplikat erkannt, und das Dokument wird von der KI gelesen, wenn Ihr Tarif das enthält. Benötigtpackaging:write.
Warum zwei Schritte
Die Datei geht nicht durch die API, sondern direkt in den Speicher. Der Grund ist die Plattform: Anfragen sind auf rund 4,5 MB begrenzt, Dokumente dürfen bis 10 MB groß sein. Einen Multipart-Upload in einem Aufruf gibt es deshalb nicht. Der Ablauf ist:- Upload-URL anfordern — mit Dateiname, MIME-Typ und Größe.
- Die Datei per
PUTan diese URL senden, mit demselbenContent-Type, innerhalb von 10 Minuten. - Den zurückgegebenen
s3KeyalsstorageKeyanPOST /api/v1/ppwr/documentsübergeben.
fileSize oder eine
fileSize über dem Limit ist ein 400, und es wird keine URL ausgestellt. fileSize muss die exakte
Bytezahl der Datei sein (eine ganze Zahl größer als 0): Sie ist in die URL signiert, und einen PUT
mit jeder anderen Länge lehnt der Speicher mit 403 ab.
Das Ziel: eine UUID, kein Schlüssel
target ist genau eines von { "packagingSpecId": "<id>" } (Verpackungseinheit) oder
{ "componentId": "<id>" } (Komponente), jeweils die UUID. Die Abgleichschlüssel des Intakes
(internalRef, code) adressieren hier nichts — ein Komponenten-Code ist nicht eindeutig (siehe
Stammdaten zurücklesen). Lösen Sie die ID vorher auf:
GET /api/v1/ppwr/units?internalRef=U-1 bzw. GET /api/v1/ppwr/components?code=K-1.
Beispiel
<UNIT_ID> ist die id aus GET /api/v1/ppwr/units?internalRef=U-1, <S3_KEY> der s3Key aus
Schritt 1. fileName ist in Schritt 3 ein Pflichtschlüssel; sein Wert darf null sein.
Dokumenttypen
type ist immer Pflicht. Ein Anhängen ohne type ist ein 400; ein Upload ohne Typ, den die
KI erst einordnet, ist über die API nicht möglich.
Ein Typ, der nur an die Verpackungseinheit gehört, wird an einer Komponente mit
404 { "error": "target" } abgelehnt — wie im App-Dialog, der ihn dort gar nicht anbietet. Was die KI
aus welchem Typ liest, beschreibt die Seite Nachweise.
KI-Auswertung: extractionQueued und aiSkippedReason
Das Ablegen hängt nie an der KI. Ein 201 heißt: Das Dokument ist gespeichert und an der
Verpackungseinheit bzw. Komponente sichtbar. Ob es zusätzlich ausgewertet wird, sagt die Antwort:
extractionQueued: true— die Auswertung ist eingereiht; Vorschläge erscheinen danach in der Prüfansicht der App.extractionQueued: falsemitaiSkippedReason— die Auswertung wurde nicht eingereiht:disabled(KI-Zusatzmodul nicht aktiv),quotabzw.spend(ein Monatslimit ist erreicht) oderenqueue(das Einreihen ist fehlgeschlagen — das Dokument ist gespeichert, stoßen Sie die Analyse in der App erneut an).
Duplikate und Wiederholungen
Liegt in Ihrem Unternehmen bereits ein aktives Dokument mit exakt denselben Bytes, wird nichts geschrieben:409. anchor nennt, woran das vorhandene Dokument hängt (kind ist unit oder
component), oder ist null. Wollen Sie die Datei trotzdem ein zweites Mal anhängen (etwa an eine
andere Verpackungseinheit), senden Sie denselben Body mit "allowDuplicate": true erneut — die
hochgeladene Datei wird dafür vorgehalten, ein neuer Upload ist nicht nötig.
Liegt die einzige Kopie mit diesen Bytes im Papierkorb, wird sie wiederhergestellt und an das neue
Ziel gehängt: Sie erhalten 201 mit der ursprünglichen id dieses Dokuments, kein 409.
Fehler
Nicht unterstützt
- Dokumente an Mehrwegsystemen (Pool-Dokumente) — nur in der App.
- Adressierung über
internalRefodercode— nur über die UUID. - Upload in einem einzigen Aufruf (Multipart) — wegen der Größengrenze der Plattform, siehe Warum zwei Schritte.
- Antworten von Lieferanten — die kommen über das Lieferantenportal, nicht über diesen Endpunkt.
Konformitätserklärungen lesen
Lesen Sie zurück, ob eine Verpackungseinheit eine Konformitätserklärung (DoC) hat, welche, und wo ihr PDF liegt. Gleiche Voraussetzungen wie bei den übrigen Lese-Endpunkten:packaging:read,
Kundenarbeitsbereich, PPWR-Modul.
GET /api/v1/ppwr/units?include=doc
include=doc ist optional (es kostet einen zusätzlichen Lesezugriff pro Seite); ohne den Parameter
bleiben die Zeilen unverändert. Jeder andere include-Wert ist ein 400. Jede Zeile erhält:
-
docState— derselbe Wert, den die Einheitentabelle der App zeigt: -
activeDeclaration— die aktive Erklärung der Verpackungseinheit, odernull:
fields akzeptiert docState und activeDeclaration wie jeden anderen Schlüssel; wer eines davon
nennt, bekommt include=doc automatisch — ein ausgewähltes Feld fehlt nie.
Erklärungen auflisten und abrufen
GET /api/v1/ppwr/declarations und GET /api/v1/ppwr/declarations/{id} liefern dieselben Felder wie
activeDeclaration, plus specIds (alle Verpackungseinheiten, die die Erklärung je abgedeckt hat —
eine Familien-DoC ist eine Zeile) und activeSpecIds (die Verpackungseinheiten, für die sie
gerade die aktive DoC ist). Eine Familien-DoC bleibt issued, wenn eine neuere DoC einige ihrer
Einheiten übernimmt; diese Einheiten fallen dann aus ihren activeSpecIds, bleiben aber in
specIds. Eine Familien-DoC trägt supersedesId: null. Nie den signierten Snapshot oder die Datei
selbst. pdfUrl ist null bei einem Entwurf und bei einer gesammelten Erklärung, deren hochgeladene
Datei nicht mehr vorliegt (verified ist dann ebenfalls false). supersededAt ist der Zeitpunkt, zu
dem eine neuere Erklärung diese für ihre letzte Verpackungseinheit abgelöst hat; null, solange
status nicht superseded ist.
Filter, alle optional, jeder höchstens einmal (ein leerer Wert ist kein Filter, ein fehlerhafter ein
400):
limit / offset wie bei den anderen Listen (Standard 100, max. 1000). Sortiert nach issuedAt,
neueste zuerst, Entwürfe zuletzt.
changedSince trifft eine Erklärung, die zu oder nach dem Zeitpunkt ausgestellt, zurückgezogen
oder erneut geprüft wurde (unabhängig vom Ergebnis der Prüfung), oder eine gesammelte, deren
hochgeladene Datei abgelaufen ist (sie liest dann verified: false), sowie eine, die in dieser
Zeit eine Verpackungseinheit an eine neuere Erklärung verloren hat — eine abgelöste Erklärung
ebenso wie eine Familien-DoC, die mit weniger activeSpecIds issued bleibt. Entwürfe treffen nie.
404 auf /declarations/{id} für eine unbekannte, fehlerhafte, gelöschte oder die eines anderen
Unternehmens.
PDF herunterladen
GET /api/v1/ppwr/declarations/{id}/pdf liefert die Datei hinter pdfUrl.
- Eine erzeugte DoC wird aus dem bei der Unterzeichnung eingefrorenen Snapshot gerendert, nach
erneuter Prüfung seines Hashs.
langwählt die Sprache: genau einer der Wertede en fr es it nl bg cs da el et fi hr hu lt lv pl pt ro sk sl sv, ohne Rücksicht auf Groß-/Kleinschreibung (EN=en). Jeder andere Wert —ga, ein Tippfehler — ist ein400, der die unterstützten Werte nennt. Ohnelang(oder mit leerem Wert) kommt Deutsch. lang=allliefert ein ZIP mit einem PDF je Sprache, die die Märkte der Verpackungseinheit verlangen (Art. 39 Abs. 2), ohne Irisch und Maltesisch. Bei einer Familien-DoC kommen die Sprachen von einem aktiven Mitglied (dem mit der kleinsten ID). Eine zurückgezogene oder abgelöste DoC hat kein aktives Mitglied; ihr ZIP enthält nur das deutsche PDF.- Eine gesammelte DoC ist die hochgeladene Datei des Herstellers, unverändert, als PDF, PNG oder
JPEG mit passendem
Content-Type(langgilt dort nicht). Wurde diese Datei gelöscht, lautet die Antwort404, obwohlpdfUrlgesetzt ist.
ERP-Rezept — die DoC in den Artikelstamm zurückschreiben
- Fragen Sie
GET /api/v1/ppwr/declarations?changedSince=<letzter Lauf>ab (ohnestatus-Filter). - Speichern Sie bei jeder
issued-Zeile mitverified: truederenreferenceNumberund die Datei auspdfUrlan jeder Verpackungseinheit inactiveSpecIdsund überschreiben Sie, was diese Einheit bisher hatte. So wird auch eine abgelöste DoC ersetzt: Ihre Nachfolgerin taucht in der Abfrage auf, die abgelöste ebenfalls (übergehen Sie sie). Verwenden Sie hier nichtspecIds— dort stehen auch Einheiten, die eine neuere DoC übernommen hat. - Eine Zeile mit
verified: falseliegt nicht vor (eine gesammelte DoC, noch nicht verifiziert, oder ihre Datei ist abgelaufen): Entfernen Sie, was Sie zu dieser Erklärung gespeichert haben. - Bei
withdrawnentfernen Sie, was Sie zu dieser Erklärung gespeichert haben, an jeder Verpackungseinheit inspecIds. - Abgleichen Sie ab und zu mit
GET /api/v1/ppwr/units?include=doc:issuedheißt, eine verifizierte DoC liegt vor und ist aktuell;outdatedundin_reviewbrauchen Arbeit in der App;ready,blockedundcollecthaben noch keine DoC;not_applicablebraucht keine.
Mengen-Feed & Tonnage
Mengen je SKU, Markt und Jahr
Der wiederkehrende Feed: ein Auszug aus Vertrieb/Fakturierung, eine Zeile je SKU × Markt × Jahr.202 mit { "jobId": …, "status": "queued", "statusUrl": … } und einem
Location-Header. statusUrl und Location zeigen auf /api/import-jobs/{jobId}, das ist derselbe
Endpunkt wie /api/v1/import-jobs/{jobId}. Fragen Sie ihn ab (benötigt packaging:read), bis
status final ist — siehe Stapelimport (asynchron).
Der ganze Stapel wird geprüft, bevor der Job angelegt wird, und eine fehlerhafte Zeile weist den
ganzen Stapel ab — niemals ein Job, der Minuten später scheitert:
- Jede
quantityist die absolute Gesamtmenge für diese SKU, diesen Markt und dieses Jahr und ersetzt den gespeicherten Wert — sie wird nie dazuaddiert. Senden Sie niemals Zuwächse, Retouren oder Gutschriften als eigene Zeilen: Eine Gutschrift-Zeile würde die Jahresmenge durch den Gutschriftbetrag ersetzen. Verrechnen Sie sie in der Gesamtmenge und senden Sie die korrigierte Gesamtmenge; eine Periode erneut zu senden ist der normale Weg, sie zu korrigieren. Einequantitymuss ≥ 0 sein. - Eine unbekannte
articleNumberist ein Zeilenfehler (unknownArticleNumber), niemals ein erfundener Artikel; ein innerhalb eines Stapels wiederholter Schlüssel wird beim ersten Vorkommen übernommen, die späteren Zeilen meldenduplicateInFile. - Ein Artikel, der existiert, aber mit keiner Verpackungseinheit verknüpft ist, wird angenommen und
in
imported/updatedgezählt, landet aber bei keiner Verpackungseinheit und in keinem Bericht. Nichts im Ergebnis weist darauf hin — prüfen Sie die SKU-Verknüpfungen, bevor Sie den Summen trauen. replaceScope: "period-market"erklärt den Stapel zum vollständigen Stand jeder(periodYear, market)-Kombination, die er enthält: Gespeicherte Zeilen genau dieser Kombinationen, die der Stapel nicht enthält, werden gelöscht. Es zählen nur Kombinationen aus Zeilen, deren Artikelnummer gefunden wurde. Eine Kombination, die der Stapel gar nicht enthält, bleibt unberührt — ein Markt, in dem Sie nicht mehr verkaufen, behält seine alten Zeilen; um ihn auf null zu setzen, senden Sie seine SKUs mitquantity: 0. OhnereplaceScopedarf ein Stapel SKUs weglassen, die sich nicht geändert haben. Senden SiereplaceScopenur als"period-market", sonst lassen Sie es weg.- Mengen je Verpackungseinheit werden abgeleitet, nicht gesendet. Die in Verkehr gebrachte Menge einer Verpackungseinheit ist die Summe der Mengen ihrer verknüpften SKUs. Eine Verpackungseinheit, die alle verknüpften Mengen verliert, zählt keinen Feed-Wert mehr.
result des Jobs enthält
{ imported, updated, deleted, failed[], derived, cleared, conflicts[], multiLinkedArticleNrs[] }.
Für die Perioden und Märkte, die der Stapel berührt, ist derived die Zahl der Verpackungseinheiten,
deren gezählte Werte nach dem Stapel aus dem Feed stammen, und cleared die Zahl derer, die vor dem
Stapel Feed-Werte zählten und danach keine mehr. conflicts listet die Verpackungseinheiten, deren in
der App eingetippte Werte Vorrang vor dem Feed haben (siehe unten).
multiLinkedArticleNrs ist ein Hinweis, kein Fehler. Es listet die Artikelnummern, deren
Menge in mehr als eine Verpackungseinheit fließt — Artikel und Verpackungseinheit stehen
absichtlich in einer n:m-Beziehung (z. B. Verkaufs- und Transportverpackung), also landet dieselbe
Menge bei jeder verknüpften Verpackungseinheit. Prüfen Sie diese Artikel auf Doppelzählung.
Blockierend ist der Hinweis nie.Feed-Werte werden bei jedem Lesen berechnet; Änderungen an Verknüpfungen und SKUs wirken ohne
erneute Übertragung.
Tonnage je Materialart
Der Aggregat-Modus in Kilogramm, für Inverkehrbringer, deren Verpackungszusammensetzung nicht Einheit für Einheit modelliert ist — und zugleich der Weg, mehrdeutige Materialfamilien zu deklarieren (sieheambiguous_material weiter unten). Nur LUCID (Deutschland).
Zeilen sind { periodYear, market, zsvrCategory, tonnageKg, note? }, als bloßes Array oder in
rows. zsvrCategory ist eine der acht VerpackG-Materialarten, gesendet als JSON-String
("50000", nicht 50000): 10000 Glas, 20000 Papier/Pappe/Karton, 30000 Eisenmetalle, 40000
Aluminium, 50000 Kunststoffe, 60000 Getränkekartonverbunde, 70000 sonstige Verbunde, 80000
sonstige Materialien. tonnageKg ist eine JSON-Zahl — anders als bei den Stammdaten-Zeilen wird
hier kein Zahlen-String umgewandelt. market nimmt zwei beliebige Buchstaben an und speichert sie,
gelesen wird aber nur DE.
Die Antwort ist 200 mit { written, skipped, failed, invalid }:
failed und invalid sind { index, identifier, error }; identifier ist die
zsvrCategory der Zeile, wenn sie als String gesendet wurde (sonst leer), und ein Fehler in
invalid ist der englische Text des Validators. Ein leerer Stapel ist 200 mit leeren Arrays; mehr
als 1.000 Zeilen sind 400 (Too many rows: max 1000 per request). Ein im Stapel wiederholter
Schlüssel: Die letzte Zeile gewinnt, und beide Zeilen stehen in written.
DELETE /api/v1/ppwr/tonnage?periodYear=2026&market=DE&zsvrCategory=50000 löscht eine Tonnage-Zeile;
danach zählt wieder die aus SKUs abgeleitete Masse dieser Materialart. Eine in der App eingetragene
Zeile wird nicht gelöscht: 409 mit code: "manualOverridePresent" — löschen Sie sie in der App. Ein
unbekannter Schlüssel ist 404, auch bei einer Wiederholung nach einem Erfolg.
Tonnage ist zugleich der Ausweichmodus: Sind die Daten zur Verpackungszusammensetzung (noch) zu
dünn für eine Stücklisten-Modellierung, überspringen Sie die Schritte 1–3 des
Integrationsrezepts und senden nur Kategorie-Summen. Um eine
Materialart später auf SKU-basierte Werte umzustellen, löschen Sie ihre Tonnage-Zeilen (in der App
oder mit DELETE /api/v1/ppwr/tonnage); bis dahin wirkt der Mengen-Feed auf diese Materialart nicht.
Mengenmeldung-Berichte
Register und Märkte
Jedesformat gehört zu genau einem Register und einem Markt — senden Sie den passenden market.
Ein abweichender Markt (z. B. verpact_nl mit DE) wird mit 400 abgelehnt.
Die Nicht-LUCID-Register kennen nur Portalformulare. Ihre Datei ist eine deterministische
Übertragungshilfe — die Werte je Kategorie in den Kategorien, Maßeinheiten und der Reihenfolge des
jeweiligen Portals —, die ein Mensch dort eintippt. Mehr kann eine Datei dort nicht automatisieren.
Bericht erzeugen
reportTypeist fürlucid_dePflicht.systemOperatorIdist fürlucid_dePflicht, außer in der App ist eine gültige SystemOperatorID hinterlegt: Fehlt sie dann in der Anfrage, gilt die hinterlegte, und ein gesendeter Wert hat Vorrang. Der201nennt die verwendetesystemOperatorId. Die Übertragungshilfe-Register verwenden sie nicht (diese erzeugen immer den Jahresbericht); eine fehlerhaftesystemOperatorIdist dort trotzdem ein400.reportTypeist die LUCID-Meldeart:HPM1(Planmengenmeldung),HMM1(unterjährige Mengenmeldung),HJM1(Jahresabschlussmengenmeldung),HNM1(Ergänzungsmengenmeldung),HAM1(Abzugsmengenmeldung).systemOperatorIdist die LUCID-Nummer Ihres dualen Systems: zwei Buchstaben und 13 Ziffern (Kleinschreibung wird angenommen und hochgesetzt).marketist ISO-Alpha-2 (Groß-/Kleinschreibung egal),periodYeardas Kalenderjahr (2000–2100).
201 — { "id": "…", "version": 2 } (bei lucid_de zusätzlich mit systemOperatorId): Der
Bericht wurde erzeugt und eingefroren. Nach einer Datenänderung erneut zu erzeugen, ist der
Normalfall: Der neue Bericht löst den vorherigen derselben Meldeart ab (version zählt hoch, die alte
Zeile bleibt mit status: "superseded" gelistet).
409 — { "error": "reportAlreadyGenerated" }: Zwei Erzeugungen für denselben Bereich liefen
gleichzeitig, eine hat gewonnen. Liste erneut abrufen und bei Bedarf wiederholen.
422 — die Daten sind noch nicht meldefähig; es wurde nichts geschrieben:
{ "error": "nothing_to_declare" } (mit leerem blockingErrors) bedeutet, dass es für diese Periode
und diesen Markt keine meldefähigen Zahlen gibt — bei LUCID weder abgeleitete Mengen noch
Tonnage-Werte, bei den Übertragungshilfe-Registern keine abgeleiteten Mengen (Tonnage-Werte speisen
nur LUCID). Ist jede in Verkehr gebrachte Verpackungseinheit eine Mehrwegverpackung, werden auch die
Tonnage-Werte ignoriert, und die Antwort ist ebenfalls nothing_to_declare.
Berichte auflisten
Optionale Query-Parameter:periodYear, market, status (generated oder superseded) sowie
limit und offset — das Paging greift nach den Filtern. Ein ungültiger Wert eines bekannten
Parameters ist ein 400; unbekannte Parameternamen werden ignoriert.
Die Antwort ist ein JSON-Array, neueste zuerst, und enthält ausschließlich Metadaten — nie die
eingefrorene Berechnung:
status: "generated" — genau einer je Kombination aus
periodYear, market und format. Übertragungshilfe-Register melden immer reportType: "ANNUAL",
einen leeren systemOperatorId und xsdVersion: "worksheet-1".
Datei herunterladen
GET /api/v1/ppwr/mengenmeldung/{reportId}/xml liefert 200 mit der Datei als Anhang: für lucid_de
das LUCID-Datenmeldungs-XML (application/xml; charset=utf-8), für die übrigen Register die
JSON-Übertragungshilfe (application/json; charset=utf-8). Auch ein superseded-Bericht lässt sich
herunterladen. Die Datei wird aus der eingefrorenen Momentaufnahme neu erzeugt, nach erneuter
Prüfung des SHA-256 der Momentaufnahme.
403—{ "error": "…", "code": "EXPORTS_BLOCKED" }, wenn Exporte für Ihren Arbeitsbereich gesperrt sind, oder das PPWR-Modul ist nicht aktiv.404— unbekannte oder fehlerhafte Berichts-ID, einschließlich jeder ID eines anderen Unternehmens.409—{ "error": "Report integrity check failed" }: Die Momentaufnahme passt nicht mehr zu ihrem Hash; die Datei wird verweigert statt ausgeliefert.
Integrationsrezept ERP zu LUCID
Schritt 1 ist ein Datenprojekt, alles danach ist Routine. Die Verpackungsstammdaten auf Meldequalität zu bringen, ist echte Arbeit — einmalig, und erneut nur bei Sortimentsänderungen. Der wiederkehrende Teil (Mengen rein, Bericht raus) ist ein geplanter Job und zwei HTTP-Aufrufe.1
Einmalig: Verpackungsstammdaten
Legen Sie zuerst einen API-Schlüssel an; einer mit
packaging:write deckt alles ab. Das Register
deklariert Masse je Materialart, jede verkaufte SKU muss sich also in Komponenten mit
monomateriellen Gewichten auflösen:- Komponenten (
POST /api/v1/ppwr/components) mitmaterialsundmassGrams. - Verpackungseinheiten (
POST /api/v1/ppwr/units) — die verkaufsfähige Verpackung, verknüpft mit Komponenten überPOST /api/v1/ppwr/links. Eine Verpackungseinheit in mehreren Größen deklariert diese auf der Zeile selbst (sizes[]); ihre Komponenten-Massen je Größe kommen aus Stücklisten-Zeilen mitformatReference. - SKU-Verknüpfungen —
articleNumbersauf der Einheiten-Zeile oderPOST /api/v1/ppwr/article-links: welcher Artikel in welcher Verpackungseinheit ausgeliefert wird. Das ist es, was später Verkaufszahlen in Verpackungsmassen übersetzt.
2
Wiederkehrend: Mengen je SKU, Markt und Jahr
Ein geplanter Export aus Vertrieb/Fakturierung an
POST /api/v1/ppwr/quantities, danach
GET /api/v1/import-jobs/{jobId} abfragen. Die Frequenz bestimmen Sie. Jede Zeile trägt die
absolute Gesamtmenge für ihre SKU, ihren Markt und ihr Jahr — verrechnen Sie Retouren und
Gutschriften in dieser Gesamtmenge; wer für eine Periode und einen Markt den vollständigen Stand
exportiert, sendet replaceScope: "period-market".3
Bericht erzeugen
POST /api/v1/ppwr/mengenmeldung/reports für Periode, Markt und Register, bei LUCID mit Ihrer
systemOperatorId und dem reportType. Ein 422 nennt Ihnen Eintrag für Eintrag, welche Daten
noch nicht meldefähig sind — beheben, erneut senden, erneut erzeugen. Ein 201 ist ein
eingefrorener, versionierter Bericht.4
Datei abholen
GET /api/v1/ppwr/mengenmeldung/reports?periodYear=2026&market=DE&status=generated, dann
GET /api/v1/ppwr/mengenmeldung/{id}/xml. Hash-geprüft; das LUCID-XML entspricht dem
veröffentlichten ZSVR-XSD.5
Bei LUCID hochladen — der eine Klick, der nicht automatisiert wird
Bei LUCID anmelden, Datenmeldung öffnen, XML hochladen.