Zum Inhalt springen

Changelog

Changelog

Kuratierte Highlights der letzten Releases. Für vollständige API-Versionen und Breaking-Change-Policy siehe die API-Versionierung & LTS-Policy.

Detaillierte Änderungen an einzelnen Endpoints: OpenAPI-Spezifikation und interaktive API-Referenz.


2026-08 · operationId für alle 75 Operationen, und eine Wann-Anleitung für Agenten

  • Ergänzung: Alle 75 Operationen der OpenAPI-Spezifikation tragen jetzt eine operationIdlistCodes, createCode, getCodeStats, archiveWorkspace und so fort. Bisher fehlte das Feld durchgehend, sodass jeder Generator den Methodennamen aus HTTP-Methode und Pfad ableiten musste (postV1Codes). Solche Namen hängen am Pfad und ändern sich mit jedem Pfad-Umbau. Namensform an allen 75 Stellen gleich: list/get/create/update/replace/delete, sonst das Verb der Fachhandlung (validateDpp, registerGs1Identifier, pingWebhook). Das Verb folgt der Fachlichkeit, nicht der HTTP-Methode — archiveWorkspace ist ein DELETE, importDpps ein POST.
  • Auswirkung auf selbst generierte Clients: Wer sein SDK aus der Spezifikation generiert, bekommt beim nächsten Lauf umbenannte Methoden (postV1CodescreateCode). Das ist der Zweck der Änderung, aber es ist ein Umbenennen in fremdem Code — deshalb hier ausdrücklich genannt. Pfade, Parameter, Antwortformen und Statuscodes sind unverändert; die offiziellen SDKs, CLI und MCP-Server sind nicht betroffen.
  • Agenten-Einstieg: https://qr3.app/llms.txt hat einen Abschnitt ## When to use qr3.app — sechs Aufgaben statt sechs Funktionen, plus den Satz, wofür qr3.app nicht das richtige Werkzeug ist. Dieselbe Auskunft liefert der MCP-Server jetzt im Handschlag: initialize gegen https://mcp.qr3.app/mcp antwortet mit einem Feld instructions (vorher: elf Werkzeuge ohne jede Einordnung). Enthalten ist auch, welche Aufrufe einen API-Schlüssel brauchen — initialize und tools/list nicht, jedes tools/call schon.
  • Korrekturen in llms.txt: Vier Angaben wurden gegen Produktion gemessen und hielten nicht: (1) das Python-SDK qr3app ist auf PyPI nicht veröffentlicht — die pip install-Zeile ist ersatzlos raus; (2) Accept: text/markdown gilt für Startseite und Blog, nicht für jede serverseitig gerenderte Seite; (3) die Kontaktadresse lautet [email protected], auch im JSON-LD der Startseite; (4) /de/security/ ist eine Weiterleitung auf einen Anker, keine eigene Seite. Neu verlinkt: docs.qr3.app/de/skills/.
  • Absicherung: Drei Zusicherungen in openapi-spec.test.ts — Vollzähligkeit, Eindeutigkeit, lowerCamelCase —, jede mit einer Mutation gegengeprüft. Neun weitere Tests halten die vier korrigierten Angaben fest, damit sie nicht zurückkehren.
  • Bekannte Einschränkung: Zwei der 75 Operationen sind in der Spezifikation beschrieben, antworten aber 404 (GET /v1/codes/{id}/stats und GET /v1/account). Sie haben hier einen Namen bekommen und verlieren ihn wieder, sobald entschieden ist, ob sie gebaut oder gestrichen werden.

2026-08 · Tarif-Downgrades und Kündigungen wirken jetzt auch auf die Limits

  • Fix: Der Stripe-Webhook schrieb bei Planwechsel und Kündigung nur den Plannamen, nicht die vier Limit-Spalten der Organisation (max_workspaces, max_members, max_dynamic_codes, max_scans_per_month). Da die Durchsetzung das Maximum aus gespeichertem Wert und Plan-Baseline nimmt, behielt eine herabgestufte oder gekündigte Organisation ihre alten, höheren Limits — das Downgrade war wirkungslos. Der Webhook setzt die Spalten jetzt bei jedem echten Planwechsel auf die Baseline des neuen Tarifs und bei Kündigung auf die Free-Baseline — genau wie es die Admin-Verwaltung bereits tut.
  • Verhalten: Routine-Updates der Subscription (Verlängerung, Zahlungsmittel, Proration) fassen die Limits weiterhin nicht an; individuell gewährte höhere Limits überstehen sie unverändert. Erst ein echter Planwechsel setzt sie auf die Baseline des neuen Tarifs.
  • Auswirkung: Keine API- oder Antwortformat-Änderung. Organisationen, deren Downgrade vor diesem Fix lag, behalten die alten Werte, bis der nächste Planwechsel greift oder der Support sie anpasst.

2026-08 · Keyset-Cursor jetzt auf allen Listen-Endpoints

  • Fix: Der Cursor-Fix für GET /v1/codes und GET /v1/dpp (siehe unten) ist jetzt auf alle übrigen cursor-paginierten Listen ausgerollt: GET /v1/qr-codes/:id/comments, /v1/workspaces, /v1/gs1/identifiers, /v1/members, /v1/audit-logs, /v1/admin/orgs, /v1/admin/users und /v1/webhooks/:id/deliveries. Alle blätterten allein über created_at; Zeilen mit identischem Zeitstempel (Audit-Einträge einer Batch-Operation, Webhook-Retries, Mitglieder-Importe) konnten auf der Folgeseite verloren gehen. Sortierschlüssel ist jetzt überall das Tupel (created_at, id).
  • API-Änderung: Auf diesen Listen ist meta.pagination.next_cursor ab sofort ebenfalls ein opaker Wert (base64url) statt eines nackten Zeitstempels; unlesbare Cursor liefern 400, alte Zeitstempel-Cursor werden übergangsweise weiter akzeptiert. Ausnahme GET /v1/webhooks/:id/deliveries: dort bleibt next_cursor die ID der letzten Zustellung (unbekannte ID → erste Seite). Clients, die next_cursor unverändert zurückgeben — Dashboard, CLI, SDKs, MCP —, müssen nichts ändern.
  • Auswirkung: Keine Migration nötig. Wer in einer dieser Listen (z. B. Audit-Log oder Zustellungs-Log im Dashboard) beim Blättern Einträge vermisst hat: Sie waren nie weg — die Listen zeigen sie ab jetzt vollständig.

2026-08 · Zeitstempel nach Bearbeiten und Löschen wieder OpenAPI-konform

  • Fix: Nach PATCH /v1/codes/{id} kam updated_at im SQLite-Format ohne Zeitzone zurück (2026-08-17 09:00:00), obwohl die OpenAPI-Spezifikation format: date-time zusagt und Anlegen (POST) den ISO-Zeitstempel (2026-08-17T09:00:00.000Z) liefert. Dasselbe galt für deleted_at/updated_at beim Soft-Delete sowie für die Bearbeiten-/Löschen-Pfade von API-Keys, Organisationen, Workspaces, Mitgliedern, Kommentaren und Webhooks, außerdem für last_used_at von API-Keys und last_triggered_at von Webhooks. Alle Schreibpfade stempeln jetzt ISO 8601 (UTC, T und Z).
  • Auswirkung: Clients, die updated_at mit new Date(...) parsen (SDKs, CLI, Dashboard), lasen das Leerzeichen-Format als Ortszeit — die CLI zeigte bei einmal bearbeiteten Codes die Uhrzeit um den lokalen Offset verschoben (Wien: −2 h). Das ist behoben. Zusätzlich normalisiert eine Daten-Migration bereits gespeicherte Werte im alten Format auf ISO, damit Sortierung und Vergleiche in gemischten Beständen stimmen. Keine Änderung an Feldnamen oder Antwortstruktur.
  • Hintergrund: Gleiche Fehlerklasse wie die beiden Fixes unten (API-Key-Ablauf, Re-Scan-Cutoff): SQLites datetime('now') schreibt YYYY-MM-DD HH:MM:SS, alle übrigen Schreiber ISO 8601. Ein Quelltext-Guard-Test verhindert künftig neue Vorkommen.

2026-08 · Listen-Pagination verliert keine Batch-Codes mehr

  • Fix: GET /v1/codes und GET /v1/dpp paginierten allein über created_at. Codes aus POST /v1/codes/batch, POST /v1/dpp/batch und dem CSV/XLSX-Import teilen sich aber einen Zeitstempel — sobald ein Batch größer war als limit (Default 20), lieferte die zweite Seite die restlichen Zeilen desselben Zeitstempels nicht mehr. Die Codes existierten und waren per GET /v1/codes/:id erreichbar, tauchten in der Liste (Dashboard, CLI qr3 list, SDKs, MCP) aber nie auf. Der Cursor ist jetzt ein Keyset über (created_at, id).
  • API-Änderung: meta.pagination.next_cursor ist ab sofort ein opaker Wert (base64url) statt eines nackten Zeitstempels. Wer den Cursor unverändert als ?cursor= zurückgibt — so wie Dashboard, CLI, alle SDKs und der MCP-Server es tun — muss nichts ändern. Alte Zeitstempel-Cursor werden übergangsweise weiter akzeptiert; unlesbare Cursor liefern jetzt 400 statt stillschweigend die erste Seite.
  • Auswirkung: Wer nach einem Batch-Import in der Liste weniger Codes gesehen hat, als angelegt wurden: Die Codes waren nie weg — die Liste zeigt sie ab jetzt vollständig. Keine Migration nötig.

2026-08 · Sicherheits-Re-Scans laufen wieder im 24-Stunden-Takt

  • Fix: Das periodische Re-Scanning von Ziel-URLs und Landingpage-Links (Google Web Risk) übersprang Codes, deren letzter Scan am selben Kalendertag wie der 24-Stunden-Cutoff lag — je nach Uhrzeit verzögerte sich der Re-Scan um bis zu einen weiteren Tag. Der Cutoff wird jetzt im selben ISO-Format berechnet, in dem die Scan-Zeitstempel gespeichert sind.
  • Auswirkung: Eine Ziel-URL, die nach dem letzten Scan als unsicher eingestuft wird, führt wieder innerhalb des dokumentierten 24-Stunden-Fensters zum automatischen Pausieren des Codes. Keine API- oder Antwortformat-Änderung.

2026-08 · API-Keys laufen zum Ablaufzeitpunkt ab

  • Fix: Ein API-Key, dessen expires_at am selben Tag lag, wurde bis Mitternacht UTC weiter akzeptiert. Der Ablauf wird jetzt als Zeitstempel verglichen statt als Zeichenkette — ein abgelaufener Key liefert sofort 401.
  • Hintergrund: expires_at wird als ISO-Zeitstempel gespeichert (2026-08-14T09:00:00Z), die Vergleichsseite lieferte das Leerzeichen-Format (2026-08-14 09:00:00). Der rohe Zeichenketten-Vergleich stimmte deshalb nur, solange sich schon das Datum unterschied.
  • Auswirkung: Keine Migration nötig, das Antwortformat von GET /v1/api-keys bleibt unverändert. Unlesbare Ablaufwerte gelten jetzt als abgelaufen statt als gültig.

2026-08 · API-Referenz: Tenant-Verwaltung dokumentiert

  • OpenAPI: Die Spezifikation — und damit die interaktive Referenz — dokumentiert jetzt Organisationen (inkl. GET /v1/organizations/usage), Workspaces, Mitglieder & Rollen und Audit-Logs.
  • Billing: Die Tarifübersicht (GET /v1/billing/plans) ist öffentlich; Checkout (POST /v1/billing/checkout) und Stripe-Kundenportal (GET /v1/billing/portal) sind als Endpoints für Organisations-Administratoren ausgewiesen.
  • Scan-Export: Scan-Statistiken (GET /v1/codes/{id}/scans) und der Rohdaten-Export (…/scans.csv, …/scans.xlsx) sind vollständig dokumentiert — inklusive DSGVO-Hinweis: Der ip_hash ist nie im Export enthalten.
  • Fehlerverhalten: Neu dokumentiert ist auch die 400-Antwort der Request-Validierung: Der Body ist der rohe Zod-Fehler, kein RFC-7807-Problem-Dokument — ausgeliefert wird er dennoch unter dem Content-Type application/problem+json.
  • Dashboard: Öffentliche Dateien auf der Code-Detailseite haben jetzt einen Button, der ihren öffentlichen Link in die Zwischenablage legt – direkt als Ziel-URL eines QR-Codes verwendbar, wenn ein Scan sofort ein bestimmtes Dokument öffnen soll statt der Landingpage mit der Dateiliste.
  • API: Die Datei-Endpoints (/v1/files) liefern zusätzlich public_url. Das Feld ist nur bei Dateien mit visibility: public gesetzt – private Dateien bekommen keine öffentliche Adresse.
  • Verhalten: Der Link braucht keine Anmeldung und öffnet die Datei direkt im Browser. Ein Ersetzen der Datei lässt ihn unverändert, ein darauf gedruckter Code bleibt also gültig. Details: Dateien & Datenblätter.

2026-07 · Team-Rollen: Bearbeiter ohne Löschen & Admin-Abrechnung

  • Neu: Mitgliederrolle Bearbeiter (ohne Löschen) — legt QR-Codes, Dateien und Digital Product Passports an und bearbeitet sie, kann aber nichts löschen und keine API-Keys anlegen. Alle destruktiven Endpoints prüfen die Rolle serverseitig (403).
  • Abrechnung: Tarif-Upgrades und das Stripe-Kundenportal (POST /v1/billing/checkout, GET /v1/billing/portal) sind jetzt Organisations-Administratoren vorbehalten — alle anderen Rollen sehen eine schreibgeschützte Tarifübersicht.
  • Dashboard: Aktionen, die die eigene Rolle nicht zulässt, werden ausgeblendet: Ein Betrachter sieht z. B. keine Buttons zum Anlegen, Bearbeiten oder Löschen; Listen, Downloads und Statistiken bleiben sichtbar. Details: Team & Rollen.
  • Landingpage: Die von qr3 gehostete Landingpage eines Codes kann jetzt externe, selbst gehostete Links ({ label, url }) auflisten – zusätzlich zu oder anstelle von hochgeladenen Dateien, etwa für Datenblätter auf der eigenen Seite.
  • API: POST/PATCH /v1/codes akzeptieren ein links-Array (0–20 Einträge, http(s), ≤ 2048 Zeichen). Jede URL wird mit Google Web Risk geprüft; eine unsichere URL liefert 422. Ein leeres Array löscht alle Links.
  • Dashboard: Links auf der Code-Detailseite hinzufügen, sortieren und entfernen.
  • Sicherheit: Gerenderte Links bleiben XSS-sicher (escaped, nur http(s)) und die Seite behält ihren noindex-Header.

2026-04 · Dashboard-Analytics pro QR-Code

  • Dashboard: Der Analytics-Button in der QR-Code-Liste öffnet jetzt die Statistikseite des jeweiligen QR-Codes unter /dashboard/codes/{id}.
  • Routing: Der Alias /dashboard/codes leitet weiterhin auf /dashboard um, fängt aber keine Detailrouten wie /dashboard/codes/{id} mehr ab.
  • API: Die Detailseite lädt den QR-Code direkt per GET /v1/codes/:id; dadurch ist sie nicht mehr von Listen-Pagination-Limits abhängig.
  • Tests: Regressionstests decken den Alias-Redirect und den direkten Code-Load ab.

2026-04 · Dashboard-Löschdialog für QR-Codes

  • Dashboard: Der Mistkübel in der QR-Code-Liste öffnet jetzt einen eigenen React-Dialog statt eines nativen Browser-Popups.
  • Feedback: Nach dem Löschen erscheint eine Toast-Rückmeldung für Erfolg oder Fehler.
  • Tests: packages/dashboard/tests/dashboard.test.ts verhindert Regressionen auf confirm() im QR-Code-Löschflow.
  • Dashboard: Shortcodes in der QR-Code-Liste sind jetzt direkt als externe Redirect-Links klickbar. Das External-Link-Icon neben z. B. wu3qaa öffnet https://qr3.app/{shortCode} in einem neuen Tab.
  • i18n: Tooltip-Texte für Deutsch und Englisch ergänzt.
  • Tests: packages/dashboard/tests/dashboard.test.ts schützt Link-Href, neues Tab-Verhalten, noopener noreferrer und Icon gegen Regressionen.

2026-04 · Redirect-Worker-Route für dynamische QR-Codes

  • Fix: Dynamische QR-Codes unter https://qr3.app/{shortCode} werden wieder vom Redirect-Worker verarbeitet. Die Production-Route nutzt jetzt qr3.app/*, weil Cloudflare Worker-Routen keine :code-Pfadparameter unterstützen.
  • Härtung: Nicht passende Pfade werden an die Landing-Origin durchgereicht, damit normale Seiten wie /de/pricing nicht vom Redirect-Worker blockiert werden.
  • Tests: packages/redirect/tests/unit/redirect.test.ts prüft die Wildcard-Route, Shortcode-Verarbeitung und Origin-Pass-through.

2026-04 · Workspace-DPP-Scan-Übersicht (Q3.4.2)

  • Neu: GET /v1/workspace/stats/dpp?days=30 — aggregiert alle dpp_scans des API-Key-Workspaces (active_dpps, scans_by_day, top_dpps mit Produktname/Kategorie).
  • Dashboard: Karte auf der Startseite (/dashboard) mit 30-Tage-Balkendiagramm + Top-Listen — parallel zu den QR-Code-Karten.
  • Public: Marketing-Shortlink GET /dpp/dpp_<id> (ein Segment) für Live-Demos, parallel zu /dpp/{gtin}/{serial}.

2026-04 · DPP-Scan-Analytics (Q3.4.1)

  • Neu: GET /v1/dpp/:id/stats?days=30 — aggregierte Scans des öffentlichen GS1-Resolvers pro DPP. Felder: total_scans, period_scans, scans_by_day, top_countries, top_devices, top_representations.
  • Neu: Tabelle dpp_scans (Migration 0011) — getrennt von scans (Redirect-Worker). Die aktuelle Implementierung pseudonymisiert IP-Adressen mit HMAC-SHA-256 aus langlebigem Secret, Zweck und UTC-Tag; Original-IP-Adressen erreichen nie D1.
  • Dashboard: Mini-Chart-Karte (SVG, keine Chart-Library) auf /dashboard/dpp/:dppId mit 30-Tage-Balken + Top-3-Breakdowns. Empty-State sobald ein DPP live ist aber noch keine Scans hatte.

2026-04 · Live-EU-Compliance-Simulator (Q3.3.7)

  • Neu: POST /v1/dpp/:id/validate-update — simuliert Teil-Updates stateless (Status, Markt-Liste, …) ohne Persistenz. Antwort enthält eu_compliance + preview.changed_fields.
  • Dashboard: Simulator-Karte im DPP-Detail (/dashboard/dpp/:dppId) — Chips für DE/AT/FR/IT/ES/NL + Custom, Status-Dropdown, Preview EU impact / Save changes / Reset. Non-blocking via Remix useFetcher.
  • Härtung: Ausgelagerte Simulator-Helper (readUpdatePatchFromForm, marketCountriesKey) + 18 neue Unit-Tests; Bugfix: Lone-Non-ISO-Input löscht die Marktliste nicht mehr.

2026-04 · Live-EU-Compliance-Preview im Create-Formular (Q3.3.6)

  • Geändert: POST /v1/dpp/validate liefert zusätzlich eu_compliance — derselbe Validator wie GET /v1/dpp/:id/eu-compliance, stateless vor dem Speichern.
  • Dashboard: Preview unter dem bestehenden Validation-Panel + neuer Save-Guard-Banner vor den Submit-Buttons, wenn Errors/Warnings offen sind (i18n-Pluralisierung DE/EN).

2026-04 · EU-Validator + Textil-UI (Q3.3.4 + Q3.3.5)

  • Neu: EU-Compliance-Validator mit 5 Textil-Regeln (TEXTILE_AGEC_REQUIRED, TEXTILE_MICROPLASTICS_CONSISTENCY, TEXTILE_SVHC_THRESHOLD, TEXTILE_GREENWASHING, TEXTILE_ESPR_READY).
  • Neu: GET /v1/dpp/:id/eu-compliance mit compliant / espr_ready / issues[] / summary.
  • Dashboard: EU-Compliance-Section im DPP-Detail (Summary-Tiles, gruppierte Issue-Karten, ESPR-Ready-Badge im Header).

2026-04 · Textil-DPP-Schema (Q3.3.1–Q3.3.3)

  • Neu: Kategorie textile mit AGEC-Pflichtchain (Weben/Stricken → Färben/Drucken → Konfektion), pro Faser origin_country + recycled_pct, svhc_substances[], ESPR-Opt-in (PEF, Lebensdauer, Recyclability).
  • Neu: Basisfeld market_countries: string[] (ISO 3166-1 alpha-2) auf allen DPP-Kategorien — steuert FR-spezifische AGEC-Regeln und die französische Consumer-Pflichtnotiz.
  • Neu: Consumer-HTML-Template mit AGEC-Mikroplastik-Warnbox, 3-stufiger Herkunftskette (Flag-Pills), SVHC-Liste, Durability- und Recyclability-Section.
  • Migration: 0010_dpp_market_countries (D1).

2026-04 · DPP-Bulk-Import (Q3.2.1–Q3.2.5)

  • Neu: POST /v1/dpp/import akzeptiert CSV und XLSX (Worker-kompatibel via SheetJS xlsx, ~283 KB gzip Bundle).
  • Skaliert: planbasiertes Limit (Free 100 → Enterprise 10k) + chunked db.batch() à 100 + 5 MB Body-Limit.
  • Neu: Fehlerreport als CSV im errors_csv-Feld der 201-Antwort; GET /v1/dpp/import/templates/:category?format=csv|xlsx liefert fertige Vorlagen für Batterie und Textil.
  • Dashboard: Drag-and-Drop-Upload unter /dashboard/dpp/import mit Template-Proxy und Inline-CSV-Download.

Nicht-Breaking — LTS-Erweiterungen

Alle oben genannten Änderungen sind additiv:

  • Bestehende POST /v1/dpp/validate-Clients ignorieren das neue eu_compliance-Feld ohne Änderung.
  • Bestehende battery-Flows sind unverändert.
  • market_countries ist optional und defaultet auf [].

Siehe API-Versionierung für die Breaking-Change-Policy.