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
operationId—listCodes,createCode,getCodeStats,archiveWorkspaceund 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 —archiveWorkspaceist einDELETE,importDppseinPOST. - Auswirkung auf selbst generierte Clients: Wer sein SDK aus der Spezifikation generiert, bekommt beim nächsten Lauf umbenannte Methoden (
postV1Codes→createCode). 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.txthat 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:initializegegenhttps://mcp.qr3.app/mcpantwortet mit einem Feldinstructions(vorher: elf Werkzeuge ohne jede Einordnung). Enthalten ist auch, welche Aufrufe einen API-Schlüssel brauchen —initializeundtools/listnicht, jedestools/callschon. - Korrekturen in
llms.txt: Vier Angaben wurden gegen Produktion gemessen und hielten nicht: (1) das Python-SDKqr3appist auf PyPI nicht veröffentlicht — diepip install-Zeile ist ersatzlos raus; (2)Accept: text/markdowngilt 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}/statsundGET /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/codesundGET /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/usersund/v1/webhooks/:id/deliveries. Alle blätterten allein übercreated_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_cursorab sofort ebenfalls ein opaker Wert (base64url) statt eines nackten Zeitstempels; unlesbare Cursor liefern400, alte Zeitstempel-Cursor werden übergangsweise weiter akzeptiert. AusnahmeGET /v1/webhooks/:id/deliveries: dort bleibtnext_cursordie ID der letzten Zustellung (unbekannte ID → erste Seite). Clients, dienext_cursorunverä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}kamupdated_atim SQLite-Format ohne Zeitzone zurück (2026-08-17 09:00:00), obwohl die OpenAPI-Spezifikationformat: date-timezusagt und Anlegen (POST) den ISO-Zeitstempel (2026-08-17T09:00:00.000Z) liefert. Dasselbe galt fürdeleted_at/updated_atbeim Soft-Delete sowie für die Bearbeiten-/Löschen-Pfade von API-Keys, Organisationen, Workspaces, Mitgliedern, Kommentaren und Webhooks, außerdem fürlast_used_atvon API-Keys undlast_triggered_atvon Webhooks. Alle Schreibpfade stempeln jetzt ISO 8601 (UTC,TundZ). - Auswirkung: Clients, die
updated_atmitnew 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')schreibtYYYY-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/codesundGET /v1/dpppaginierten allein übercreated_at. Codes ausPOST /v1/codes/batch,POST /v1/dpp/batchund dem CSV/XLSX-Import teilen sich aber einen Zeitstempel — sobald ein Batch größer war alslimit(Default 20), lieferte die zweite Seite die restlichen Zeilen desselben Zeitstempels nicht mehr. Die Codes existierten und waren perGET /v1/codes/:iderreichbar, tauchten in der Liste (Dashboard, CLIqr3 list, SDKs, MCP) aber nie auf. Der Cursor ist jetzt ein Keyset über(created_at, id). - API-Änderung:
meta.pagination.next_cursorist 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 jetzt400statt 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_atam selben Tag lag, wurde bis Mitternacht UTC weiter akzeptiert. Der Ablauf wird jetzt als Zeitstempel verglichen statt als Zeichenkette — ein abgelaufener Key liefert sofort401. - Hintergrund:
expires_atwird 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-keysbleibt 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: Derip_hashist 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-Typeapplication/problem+json.
2026-08 · Öffentliche Datei-Links kopieren
- 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ätzlichpublic_url. Das Feld ist nur bei Dateien mitvisibility: publicgesetzt – 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.
2026-06 · Externe Links auf der Code-Landingpage
- 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/codesakzeptieren einlinks-Array (0–20 Einträge,http(s), ≤ 2048 Zeichen). Jede URL wird mit Google Web Risk geprüft; eine unsichere URL liefert422. 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 ihrennoindex-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/codesleitet weiterhin auf/dashboardum, 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.tsverhindert Regressionen aufconfirm()im QR-Code-Löschflow.
2026-04 · Dashboard-Kurzlink-Test für dynamische QR-Codes
- Dashboard: Shortcodes in der QR-Code-Liste sind jetzt direkt als externe Redirect-Links klickbar. Das External-Link-Icon neben z. B.
wu3qaaöffnethttps://qr3.app/{shortCode}in einem neuen Tab. - i18n: Tooltip-Texte für Deutsch und Englisch ergänzt.
- Tests:
packages/dashboard/tests/dashboard.test.tsschützt Link-Href, neues Tab-Verhalten,noopener noreferrerund 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 jetztqr3.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/pricingnicht vom Redirect-Worker blockiert werden. - Tests:
packages/redirect/tests/unit/redirect.test.tsprü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 alledpp_scansdes API-Key-Workspaces (active_dpps,scans_by_day,top_dppsmit 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(Migration0011) — getrennt vonscans(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/:dppIdmit 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älteu_compliance+preview.changed_fields. - Dashboard: Simulator-Karte im DPP-Detail (
/dashboard/dpp/:dppId) — Chips fürDE/AT/FR/IT/ES/NL+ Custom, Status-Dropdown, Preview EU impact / Save changes / Reset. Non-blocking via RemixuseFetcher. - 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/validateliefert zusätzlicheu_compliance— derselbe Validator wieGET /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-compliancemitcompliant/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
textilemit AGEC-Pflichtchain (Weben/Stricken → Färben/Drucken → Konfektion), pro Faserorigin_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/importakzeptiert CSV und XLSX (Worker-kompatibel via SheetJSxlsx, ~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|xlsxliefert fertige Vorlagen für Batterie und Textil. - Dashboard: Drag-and-Drop-Upload unter
/dashboard/dpp/importmit Template-Proxy und Inline-CSV-Download.
Nicht-Breaking — LTS-Erweiterungen
Alle oben genannten Änderungen sind additiv:
- Bestehende
POST /v1/dpp/validate-Clients ignorieren das neueeu_compliance-Feld ohne Änderung. - Bestehende
battery-Flows sind unverändert. market_countriesist optional und defaultet auf[].
Siehe API-Versionierung für die Breaking-Change-Policy.