{"openapi":"3.1.0","info":{"title":"Öffentliche API von QR Suisse — v1","version":"1.3.1","description":"Öffentliche REST-API von QR Suisse, verfügbar mit den Plänen Pro und Entreprise.\nDokumentiert die 9 Endpoints unter `/api/v1/*`, in ihrer Version 1.3.1. Innerhalb der v1\nsind Änderungen kompatible Ergänzungen, abgesehen von einigen Verhaltensänderungen, die im\n«Versionsverlauf» aufgeführt sind.\n\n## Authentifizierung\n\nHeader `Authorization: Bearer qrs_live_<apiKeyId>.<secret>` — eine einzige Zeichenkette,\ndie Sie unverändert aus dem Anzeigefenster im Dashboard kopieren (beim Erstellen oder\nRotieren des Schlüssels) und direkt in den Header einfügen. **Es handelt sich nicht um ein\nJWT**: Der Teil vor dem ersten `.` (nach dem festen Präfix `qrs_live_`) ist die Kennung des\nSchlüssels (direkte Suche, nie geheim), der Teil nach dem `.` ist das opake\nGeheimnis mit hoher Entropie, das auf dem Server in konstanter Zeit verglichen wird. Ein\nfehlender oder fehlerhafter Header, ein widerrufener oder unbekannter Schlüssel, ein\neingefrorener Schlüssel (siehe unten) oder ein falsches Geheimnis ergeben **alle** denselben\nFehler `401 unauthorized` — keiner dieser Fälle wird in der Antwort unterschieden (Schutz\nvor Enumeration). Jede `401`-Antwort trägt denselben Header\n`WWW-Authenticate: Bearer realm=\"qrsuisse-api\"` (RFC 6750), ohne Angabe zum Grund der\nAblehnung.\n\nDer Name des Schemas, `Bearer`, wird ohne Beachtung der Gross- und Kleinschreibung erkannt (RFC\n9110); darauf folgen ein einzelnes Leerzeichen und dann der Schlüssel. Beim Schlüssel selbst\nwird die Gross- und Kleinschreibung beachtet: Senden Sie ihn genau so, wie er kopiert wurde.\n\n**Nur HTTPS; ein über HTTP gesendeter Schlüssel muss rotiert werden.** Die API antwortet nur\nüber HTTPS. Eine über HTTP (`http://`) gesendete Anfrage wird weder umgeleitet noch\nverarbeitet: Sie erhält `403 https_required`, noch bevor der Schlüssel geprüft wird. Der\nSchlüssel wurde dann bereits unverschlüsselt über das Netz übertragen: Betrachten Sie ihn als\noffengelegt und rotieren Sie ihn im Dashboard (siehe «Rotation und Widerruf»).\n\nDer Zugang zur API insgesamt ist eine Funktion der **Pläne Pro und Entreprise** und wird bei\njeder Anfrage geprüft (nie zwischengespeichert). Jeder Plan erlaubt eine Höchstzahl\ngleichzeitig aktiver Schlüssel: **1 mit dem Pro-Plan, 10 mit dem Entreprise-Plan**. Senkt\nein Planwechsel diese Grenze, werden die überzähligen Schlüssel (die neuesten)\n**eingefroren** — weder widerrufen noch gelöscht — und sind automatisch wieder nutzbar,\nsobald wieder ein Plan mit mehr Schlüsseln aktiv ist. Endet ein Pro- oder\nEntreprise-Abonnement und wechselt das Konto zurück zum Gratis-Plan, werden **alle** seine\nSchlüssel eingefroren: Sie funktionieren sofort nicht mehr, auch nicht zum Lesen, und ergeben\n`401 unauthorized`. `403 plan_required` betrifft nur einen nicht eingefrorenen Schlüssel\neines Kontos, das keinen Pro- oder Entreprise-Plan mehr hat (Grenzfall, ausserhalb\ndes normalen Endes eines Abonnements).\n\n## Einen Schlüssel erhalten\n\nEin API-Schlüssel kann nur im Dashboard von QR Suisse erstellt werden, und zwar von einem\nMitglied eines Pro- oder Entreprise-Kontos mit der Berechtigung «Verwalter der API-Schlüssel»\n(oder Administrator); dieses Mitglied wählt auch die Rechte des Schlüssels. Es gibt kein OAuth,\nkeine automatische Client-Registrierung und keinen API-Endpoint, der einen Schlüssel ausgibt:\nEin Agent oder ein Tool kann sich keinen Schlüssel selbst beschaffen, dieses Mitglied stellt ihn\nbereit. Ein Agent ohne Schlüssel bittet daher den Benutzer, einen Schlüssel mit den nötigen\nRechten erstellen zu lassen und ihn über die Konfiguration des Agenten bereitzustellen. Der\nvollständige Schlüssel wird nur einmal angezeigt, beim Erstellen oder Rotieren: QR Suisse\nspeichert ihn nicht im Klartext und kann ihn nicht erneut senden.\n\n## Ausgesetztes Konto oder Konto, das auf die Löschung wartet\n\nWurde das Konto, dem der Schlüssel gehört, von QR Suisse **ausgesetzt** oder wartet es wegen\nInaktivität **auf die Löschung**, ergibt jede Anfrage mit einem gültigen Schlüssel\n`403 account_disabled`, **auch beim Lesen**. Die Meldung gibt an, welcher der beiden\nZustände vorliegt, nie den Grund der Aussetzung. Diese Prüfung erfolgt nach der Prüfung des\nSchlüssels und vor der Prüfung des Plans und der Rechte: Ein ungültiger, widerrufener oder\neingefrorener Schlüssel bleibt bei `401 unauthorized`. Der Schlüssel wird weder widerrufen\nnoch eingefroren: Ein neuer Schlüssel nützt nichts, der Inhaber des Kontos muss selbst\nhandeln. Wird die Aussetzung aufgehoben, funktioniert der Schlüssel automatisch wieder, ohne\nRotation. Das Warten auf die Löschung betrifft nur Konten mit dem Gratis-Plan: Nach der\nReaktivierung bleibt das Konto beim Gratis-Plan, und es gelten die üblichen Regeln\n(eingefrorener Schlüssel, also `401 unauthorized`, oder `403 plan_required`).\n\n## Atomare Rechte eines Schlüssels\n\nEin Schlüssel trägt eine Teilmenge der folgenden 5 atomaren Rechte: `QR_CREATE`, `QR_READ`,\n`QR_UPDATE`, `QR_DELETE`, `STATS_READ`. Jede Operation gibt das Recht an, das sie erfordert\n(«Erforderliches Recht») — es wird **nicht** über OAuth2-Scopes abgebildet (diese API kennt ein\neinziges Bearer-Authentifizierungsschema, keinen Standard-Scope-Mechanismus). Ein Aufruf mit\neinem authentifizierten Schlüssel ohne das erforderliche Recht ergibt `403 forbidden`.\n\n## Mandantentrennung\n\nJeder Schlüssel wirkt nur auf die Ressourcen des Kontos, das ihn ausgestellt hat. Eine\nfehlende Ressource ODER eine Ressource eines anderen Kontos ergibt **immer**\n`404 not_found` — nie `403`: Die API bestätigt nie, dass eine Ressource eines anderen Kontos\nexistiert.\n\n## Rotation und Widerruf\n\nDas **Rotieren** eines Schlüssels im Dashboard erzeugt ein neues Geheimnis und macht das alte im\nselben Moment ungültig: Ein Schlüssel hat nie zwei gültige Geheimnisse gleichzeitig. Nur das\nGeheimnis ändert sich: Der Schlüssel behält seine Kennung (den Teil vor dem `.`), seinen Namen\nund seine Rechte; solange das Tool, das ihn verwendet, den neuen Wert nicht erhalten hat, werden\nseine Aufrufe mit `401 unauthorized` abgelehnt. Das **Widerrufen** macht den Schlüssel sofort\nund endgültig unbrauchbar (`401 unauthorized`): Er kann weder reaktiviert noch rotiert werden.\nQR Suisse kann auch alle Schlüssel eines Kontos widerrufen, aktive wie eingefrorene, auf Wunsch\neines Administrators des Kontos oder wenn ein Schlüssel offengelegt oder ohne Zustimmung des\nInhabers verwendet worden sein könnte; die Administratoren des Kontos werden per E-Mail\ninformiert. Weder die Rotation noch der Widerruf ändern oder löschen einen QR-Code.\n\n## Schutz des Schlüssels\n\n- Behandeln Sie den vollständigen Schlüssel wie ein Passwort: Bewahren Sie ihn in einem\n  Secrets-Manager oder einer Umgebungsvariable auf, nie in versioniertem Code, einer geteilten\n  Datei, einem Ticket oder einem Screenshot.\n- Fügen Sie den Schlüssel nie in eine geteilte oder gespeicherte Unterhaltung ein, auch nicht\n  mit einem KI-Assistenten. Ein Agent erhält den Schlüssel über seine Konfiguration\n  (Umgebungsvariable, Secrets-Manager) und bittet den Benutzer nie, ihn in die Unterhaltung\n  einzugeben.\n- Senden Sie ihn nur im Header `Authorization`, über HTTPS, an `https://app.qrsuisse.ch/api/v1`\n  — nie in einer URL. Protokollieren Sie diesen Header nicht.\n- Geben Sie einem Schlüssel nur die nötigen Rechte: zum Beispiel `QR_READ` und `STATS_READ` (die\n  Voreinstellung «KI-Assistent» im Dashboard) für einen Agenten, der nur nachschlägt. Verwenden\n  Sie mit dem Entreprise-Plan einen Schlüssel pro Tool, damit Sie einen widerrufen können, ohne\n  die anderen zu unterbrechen.\n- Könnte der Schlüssel offengelegt worden sein, rotieren oder widerrufen Sie ihn sofort im\n  Dashboard; ist das Konto im Gratis-Plan, schreiben Sie an contact@qrsuisse.ch von der\n  E-Mail-Adresse eines Administrators des Kontos aus. Ein Agent, der vermutet, dass sein\n  Schlüssel offengelegt worden sein könnte, meldet dies dem Inhaber des Kontos.\n\n## Ratenbegrenzung (Rate Limiting)\n\nDrei unabhängige Limits:\n- **Allgemein** — 30 Anfragen / 60 Sekunden pro API-Schlüssel, gilt für **jede**\n  authentifizierte Anfrage an die 9 Endpoints.\n- **Erstellung** — 8 Anfragen / 60 Sekunden pro API-Schlüssel, gilt **zusätzlich** zum\n  allgemeinen Limit, nur für `POST /qr-codes`.\n- **PNG-Bild** — 10 Erzeugungen / 60 Sekunden **pro Konto** (von allen Schlüsseln des\n  Kontos geteilt), gilt **zusätzlich** zum allgemeinen Limit, nur für\n  `GET /qr-codes/{id}/image` im PNG-Format. Weder das SVG-Format noch eine `304`-Antwort\n  werden angerechnet. Eine mit `503 render_busy` abgelehnte Erzeugung wird ebenfalls\n  angerechnet: Ein sofortiger neuer Versuch kann bis zum Ende des Zeitfensters\n  `429 rate_limited` erhalten.\n\nEine Überschreitung ergibt `429 rate_limited` mit einem Header `Retry-After`: die Anzahl\nSekunden, bis sich das Zeitfenster des ausgeschöpften Limits wieder öffnet. Die\nLimits eines Schlüssels werden angerechnet, sobald Schlüssel und Geheimnis erkannt\nsind, auch wenn die Anfrage danach abgelehnt wird (`403 account_disabled`, `plan_required`\noder `forbidden`): Ist ein Limit ausgeschöpft, wird `429 rate_limited` zurückgegeben,\nvor diesen Ablehnungen.\n\nMit `401 unauthorized` abgelehnte Anfragen sind zusätzlich pro IP-Adresse begrenzt:\n60 Ablehnungen / 60 Sekunden pro IPv4-Adresse oder pro IPv6-Netz /64 und 300 Ablehnungen /\n60 Sekunden pro IPv6-Netz /48. Darüber hinaus gibt die API bis zum Ende des Zeitfensters\n`429 rate_limited` zurück (mit `Retry-After`), ohne den Schlüssel zu prüfen, auch wenn er\ngültig ist.\n\n**Header zur Ratenbegrenzung.** Bei den 9 dokumentierten Operationen gibt, sobald der\nSchlüssel erkannt ist, jede Antwort, Erfolg wie Ablehnung, den Zustand der von der Anfrage abgefragten Limits an, im Format des\nIETF-Entwurfs «RateLimit header fields for HTTP» (`draft-ietf-httpapi-ratelimit-headers`):\n- `RateLimit-Policy` gibt die Regel jedes Limits an: seinen Namen, `q` die Anzahl erlaubter\n  Anfragen pro Zeitfenster und `w` die Dauer des Zeitfensters in Sekunden, zum Beispiel\n  `\"api-request\";q=30;w=60, \"api-create\";q=8;w=60`;\n- `RateLimit` gibt den Zustand jedes Limits nach der Anfrage an: `r` die Anzahl verbleibender\n  Anfragen und `t` die Sekunden, bis sich das Zeitfenster wieder öffnet, zum Beispiel\n  `\"api-request\";r=29;t=60, \"api-create\";r=7;t=60`.\n\nNamen der Limits: `api-request` (allgemein), `api-create` (Erstellung), `api-image`\n(PNG-Bild, nur wenn eine PNG-Erzeugung angefordert wird). Ein Limit, das nicht abgefragt\nwurde, wird nicht aufgeführt: Nach einer Ablehnung durch das allgemeine Limit zum Beispiel\nfehlt das Erstellungslimit. Ein `401` trägt diese Header nicht, ebenso wenig eine Antwort vor jeder Prüfung des\nSchlüssels (unbekannter Pfad mit `404`, `405`, `400` wegen Kodierung, `403 https_required`); das `429` der\n`401`-Ablehnungen pro IP-Adresse trägt jene der Limits `api-unauthorized-ip` und, bei IPv6,\n`api-unauthorized-ip-net48`. Da das Format noch ein Entwurf ist, kann es sich ändern:\nMassgebend sind nur `429 rate_limited` und `Retry-After`.\n\n## Kommerzielles Erstellungskontingent\n\n`POST /qr-codes` unterliegt zusätzlich einem monatlichen Erstellungskontingent:\n**5 Erstellungen pro UTC-Kalendermonat mit dem Pro-Plan, 100 mit dem Entreprise-Plan**,\ndanach dem Guthaben der gekauften API-Erstellungspakete (Einmalkauf, nach dem monatlichen\nKontingent verbraucht, vom ältesten zum neuesten). Jede erfolgreiche Erstellung wird\ngezählt, auch wenn der QR-Code danach gelöscht wird: Eine Löschung gibt keine Erstellung\nzurück. Das monatliche Kontingent ist nicht\nübertragbar und wird am 1. jedes Monats um 00:00 UTC zurückgesetzt. Sind das monatliche\nKontingent und alle verfügbaren Pakete aufgebraucht, ergibt die Erstellung\n`429 quota_exceeded` mit dem Datum, an dem das monatliche Kontingent zurückgesetzt wird\n(`resetAt`, ISO 8601) und einem Header `Retry-After`, in Sekunden bis zu diesem Datum —\nharte Sperre, keine nutzungsabhängige Abrechnung in v1.\n\n`GET /usage` liefert jederzeit den Stand: Erstellungen des Monats, monatliches Kontingent,\nverbleibende Guthaben in den Paketen und Datum der Zurücksetzung.\n\n## Stil der QR-Codes und Vorlagen\n\nDer Stil eines QR-Codes (Farben, Formen der Module und der Ecken, Logo in der Mitte) wird im\nDashboard festgelegt, als **Stilvorlagen** des Kontos. Die API erstellt und ändert keine\nVorlage: Sie wendet eine bestehende Vorlage auf einen QR-Code an, mit dem Feld `templateId`\nvon `POST /qr-codes` und von `PATCH /qr-codes/{id}`. `GET /templates` listet die Vorlagen des\nKontos und ihre Kennung auf.\n\n- `templateId` einer Vorlage des Kontos: Ihr Stil wird auf den QR-Code übernommen, der mit\n  dieser Vorlage verknüpft bleibt. Eine spätere Änderung der Vorlage ändert den QR-Code nicht:\n  Senden Sie dieselbe `templateId` erneut, um den aktuellen Stil der Vorlage anzuwenden.\n- `templateId: null`: kein Stil, der QR-Code kehrt zur Standarddarstellung zurück (schwarz\n  auf weiss).\n- `templateId` fehlt: bei der Erstellung die **Standardvorlage** des Kontos, falls es eine\n  hat, sonst kein Stil; bei einer Änderung bleibt der Stil unverändert.\n- Eine Vorlage eines anderen Kontos, oder eine, die nicht existiert, ergibt `404 not_found`.\n- Ein Feld `style` im Anfrageinhalt wird mit `400 validation_error` abgelehnt: Ein Stil wird\n  nie direkt gesendet, nur über eine Vorlage.\n\nDer Stil gilt je nach aktuellem Plan des Kontos: Farben mit allen Plänen, Formen mit Pro und\nEntreprise, das Logo nur mit Entreprise. Der von der API zurückgegebene Stil (`style` der\nQR-Codes und der Vorlagen) und das Bild von `GET /qr-codes/{id}/image` folgen dieser Regel bei\njedem Aufruf.\n\n**Vorgeschriebene Vorlagen (Entreprise).** Ein Mitglied des Kontos mit der Berechtigung\n«Verwalter der Stilvorlagen» (oder Administrator) kann den Teammitgliedern die Vorlagen im\nDashboard vorschreiben. Solange die Vorlagen vorgeschrieben sind, muss ein API-Schlüssel eine\nVorlage des Kontos wählen: `templateId: null` wird mit `403 template_locked` abgelehnt, eine\nErstellung ohne `templateId` erhält die Standardvorlage, oder `422 template_required`, wenn\ndas Konto keine hat, und das Bild ohne Stil (`style=none`) wird mit `403 template_locked`\nabgelehnt. Ein API-Schlüssel kann weder die Vorlagen verwalten noch diese Vorgabe aufheben.\nAusserhalb des Entreprise-Plans gilt sie nicht.\n\n## Gemeinsames Fehlerformat\n\nJeder Fehler dieser API hat dieselbe Form `{ \"error\": \"<code>\", \"message\": \"<text>\",\n...allfällige Details }`. 18 stabile Codes: Ein bestehender Code ändert sich nie; eine\nNebenversion kann neue hinzufügen, nur für eine neue und optionale Funktion (siehe\n«Versionsverlauf»). Die Codes: `unauthorized` (401),\n`plan_required` (403), `forbidden` (403), `account_disabled` (403), `https_required` (403),\n`not_found` (404), `method_not_allowed` (405), `validation_error` (400), `quota_exceeded`\n(429), `rate_limited` (429), und für `POST /qr-codes` mit dem Header `Idempotency-Key`:\n`idempotency_key_mismatch` (422), `idempotency_in_progress` (409), `resource_deleted` (410),\nund für `POST /qr-codes` und `PATCH /qr-codes/{id}`: `payload_too_large` (413), Request-Body\ngrösser als 64 KiB, `template_locked` (403) und `template_required` (422), für den Stil über\nVorlagen (siehe «Stil der QR-Codes und Vorlagen»), und für `GET /qr-codes/{id}/image`:\n`template_locked` (403), `render_busy` (503, Server ausgelastet, mit `Retry-After`) und\n`internal_error` (500, Bild kann nicht erzeugt werden).\nDer Text von `message` ist eine Lesehilfe, auf Französisch wie in den Beispielen dieser\nSpezifikation, und er kann sich ohne Vorankündigung ändern. Stützen Sie Ihren Code auf den\nCode `error`, nie auf `message`.\nEin URL-Pfad mit einer ungültigen `%`-Sequenz (z. B. `%zz`) wird vor jeder\nAuthentifizierung mit `400 validation_error` abgelehnt. Ein unbekannter Pfad unter\n`/api/v1/` ergibt `404 not_found`. Eine HTTP-Methode, die ein bestehender Endpunkt nicht\nunterstützt, ergibt `405 method_not_allowed`, mit einem Header `Allow`, der die akzeptierten\nMethoden auflistet. Diese beiden Antworten sind mit und ohne API-Schlüssel dieselben. Eine\nAnfrage über HTTP erhält `403 https_required` vor all diesen Prüfungen (siehe\n«Authentifizierung»).\n\n## Öffentlicher Index\n\n`GET /api/v1` liefert ohne Authentifizierung ein statisches JSON-Dokument: Name und Version\nder API sowie Links zu ihrer OpenAPI-Spezifikation, ihrem Katalog (RFC 9727), ihrer\nDokumentation und ihrer Anleitung zur Authentifizierung. Die Spezifikation ist unter\n`https://www.qrsuisse.ch/openapi.json` (und `/openapi.yaml`) veröffentlicht, standardmässig\nauf Englisch; `?lang=de`, `fr` oder `it` liefert die übersetzte Fassung. Der Index ist auf\n60 Anfragen / 60 Sekunden pro IPv4-Adresse oder pro IPv6-Netz /64 begrenzt, und auf 300\nAnfragen / 60 Sekunden pro IPv6-Netz /48 (darüber `429 rate_limited`); seine Antworten\ntragen die Header `RateLimit` und `RateLimit-Policy` der Limits `api-index-ip` und, bei\nIPv6, `api-index-ip-net48`.\n\nDie Anleitung zur Authentifizierung ist auf Englisch unter `https://www.qrsuisse.ch/auth.md`\nveröffentlicht.\n\n## Bewährte Vorgehensweisen\n\n- Senden Sie bei jeder Erstellung einen `Idempotency-Key` mit (ein neuer Zufallswert, UUID v4\n  empfohlen, keine Personendaten) und senden Sie denselben Wert erneut, wenn die Anfrage an\n  einer Zeitüberschreitung oder einem Verbindungsabbruch scheitert (siehe `POST /qr-codes`).\n- Warten Sie bei `429 rate_limited` oder `409 idempotency_in_progress` die in `Retry-After`\n  angegebene Zeit ab, bevor Sie es erneut versuchen; versuchen Sie es bei\n  `429 quota_exceeded` nicht vor `resetAt` oder dem Kauf eines Pakets erneut (der Stand lässt\n  sich mit `GET /usage` abrufen).\n- Um `429` zu vermeiden, verlangsamen Sie, wenn sich `r` eines Limits im Header `RateLimit`\n  0 nähert, und warten Sie dessen `t` Sekunden ab, wenn es 0 erreicht.\n- Um eine gefilterte oder sortierte Liste seitenweise abzurufen, senden Sie mit `cursor`\n  dieselben Filter- und Sortierparameter erneut; nur `limit` darf sich ändern.\n- Kodieren Sie in einem Datum mit Versatz (`createdFrom`, `createdBefore`) das `+` als `%2B`,\n  oder verwenden Sie `Z`.\n- Senden Sie das `ETag` eines Bildes in `If-None-Match` zurück, um es nicht erneut\n  herunterzuladen, und bevorzugen Sie SVG für den Druck.\n- Um einen QR-Code zu gestalten, lesen Sie `GET /templates` und übergeben Sie die `id` der\n  Vorlage in `templateId`; senden Sie nie ein Feld `style`.\n- Behandeln Sie Fehler nach ihrem Code `error`, nie nach `message`.\n- Achten Sie auf den Header `Deprecation` in den Antworten: Er zeigt an, dass eine von Ihnen\n  genutzte Operation entfernt wird (siehe «Versionierung und Abkündigung»).\n\n## Versionierung und Abkündigung\n\nDie Hauptversion ist Teil der Adresse der API (`/api/v1`). Innerhalb der v1 folgen Neben- und\nKorrekturversionen den im «Versionsverlauf» beschriebenen Regeln: Eine für die v1 geschriebene\nIntegration funktioniert weiter. Eine inkompatible Änderung würde als neue Hauptversion\nveröffentlicht, unter einer anderen Adresse; ein Element der v1 wird daraus erst nach einer\nAbkündigung entfernt, nach den nachstehenden Regeln.\n\nJede neue Version wird im «Versionsverlauf» angekündigt, mit ihrem Veröffentlichungsmonat,\nihren Neuerungen und ihren Verhaltensänderungen. Die geltende Version ist jene in\n`info.version` dieser Spezifikation.\n\n**Abkündigung.** Eine Operation, ein Parameter oder eine Hauptversion wird erst entfernt,\nnachdem sie abgekündigt wurde, mit einer Frist von mindestens 6 Monaten zwischen der\nAnkündigung der Abkündigung und der Entfernung. Während dieser Frist funktioniert das\nabgekündigte Element normal weiter, und:\n- es ist in dieser Spezifikation als `deprecated` gekennzeichnet, und die Abkündigung wird im\n  «Versionsverlauf» angekündigt, mit dem Datum der Entfernung und dem Vorgehen zur Migration;\n- die Antworten der betroffenen Operation tragen den Header `Deprecation` (RFC 9745) mit dem\n  Datum der Abkündigung, den Header `Sunset` (RFC 8594) mit dem Datum der Entfernung und\n  einen Header `Link` mit `rel=\"deprecation\"`, der auf die Dokumentation der Migration\n  verweist.\n\n**Ausnahmen.** Diese Frist kann nur verkürzt oder ein Element ohne Frist entfernt werden, wenn\ndies nötig ist, um eine Sicherheitslücke zu beheben oder die Daten der Konten zu schützen, um\neine gesetzliche Pflicht oder einen behördlichen Entscheid zu erfüllen, oder weil ein Anbieter\noder ein Standard, von dem das Element abhängt (Hosting-Anbieter, Zertifizierungsstelle,\ntechnischer Standard), es ändert oder aufhebt, ohne uns diese Frist zu lassen. Wir kündigen dies\ndann so früh wie möglich im «Versionsverlauf» an und beschränken die Änderung auf das Nötige.\nDiese Regeln betreffen nicht den Zugang eines Kontos zur API, der von seinem Abonnement und den\nAllgemeinen Geschäftsbedingungen abhängt (Ende des Abonnements, Aussetzung, Widerruf eines\nSchlüssels); sie gelten im Rahmen der Allgemeinen Geschäftsbedingungen, die bei einem\nWiderspruch vorgehen.\n\nDerzeit ist keine Operation abgekündigt.\n\n## Versionsverlauf\n\nDie Version steht in `info.version` dieser Spezifikation. Innerhalb der v1 fügt eine\nNebenversion nur optionale Funktionen hinzu: Eine für die v1.0 geschriebene Integration\nfunktioniert ohne Änderung weiter, vorbehaltlich der «Verhaltensänderungen» unten. Eine\nKorrekturversion (zum Beispiel 1.1.1) behebt einen Fehler, ohne neue Funktion; sie kann eine\nAnfrage ablehnen, die dieser Dokumentation nicht entsprach. Eine inkompatible Änderung würde\nals v2 veröffentlicht, unter einer anderen Adresse.\n\n### 1.3.1 — November 2026\n\nKorrekturversion, ohne neue Funktion.\n\nVerhaltensänderungen:\n- Eine `destinationUrl` mit einem unsichtbaren oder irreführenden Zeichen wird mit\n  `400 validation_error` abgelehnt, mit Nennung des Felds: «Tag»-Zeichen (U+E0000 bis\n  U+E0FFF), Schreibrichtungs-Steuerzeichen und -Markierungen (U+202A bis U+202E, U+2066 bis\n  U+2069, U+200E, U+200F, U+061C), U+200B bis U+200D, U+00AD, Variantenselektoren und andere\n  standardmässig ignorierbare Zeichen (Unicode-Eigenschaft `Default_Ignorable_Code_Point`),\n  der ideografische Punkt U+3002 sowie jedes Kompatibilitätszeichen, das die Normalisierung\n  NFKC verändert: Buchstaben und Satzzeichen in voller Breite (U+FF01 bis U+FF5E),\n  geschütztes Leerzeichen, ideografisches Leerzeichen, Ligaturen. Eine Adresse wird nie\n  stillschweigend korrigiert: Senden Sie sie in gewöhnlichen Zeichen oder als `%XX` codiert.\n  Adressen mit Akzenten (`https://bücher.ch/café`) bleiben zulässig.\n- Das Feld `website` der Visitenkarte lehnt dieselben Zeichen ab, mit derselben Meldung.\n- Die Adresse wird so geprüft, wie sie gesendet wird, vor dem Entfernen der Leerzeichen am\n  Rand: Ein geschütztes Leerzeichen, ein ideografisches Leerzeichen oder ein U+FEFF am Anfang\n  oder am Ende der Adresse wird abgelehnt und nicht mehr stillschweigend entfernt.\n  Gewöhnliche Leerzeichen, Tabulatoren und Zeilenumbrüche am Rand werden weiterhin entfernt.\n- Auch die als `%XX` codierte Form eines unsichtbaren oder die Anzeige umordnenden Zeichens\n  wird abgelehnt (zum Beispiel `%E2%80%AE` oder codierte «Tag»-Zeichen). Die Codierung\n  anderer Zeichen (Akzente, Emoji, U+200C) bleibt zulässig.\n- Die `message` nennt das betroffene Zeichen, zum Beispiel «Champ « destinationUrl » :\n  L'adresse contient un caractère invisible ou trompeur non autorisé (…). Caractère en cause :\n  U+202E.»\n- Die Bezeichnung und die anderen Felder der Visitenkarte ändern sich nicht: Zusammengesetzte\n  Emoji (U+200D), Flaggen von Landesteilen und Zeichen in voller Breite bleiben dort zulässig.\n- Bereits gespeicherte QR-Codes werden nicht verändert. Geprüft wird nur ein gesendeter Wert:\n  Ein `PATCH` ohne `destinationUrl` prüft diese nicht.\n\n### 1.3.0 — November 2026\n\nNeu:\n- **Header zur Ratenbegrenzung** `RateLimit` und `RateLimit-Policy` in den Antworten auf\n  einen erkannten Schlüssel und in jenen des öffentlichen Index (siehe «Ratenbegrenzung\n  (Rate Limiting)»).\n- Richtlinie zu Versionierung und Abkündigung (siehe «Versionierung und Abkündigung»).\n\nVerhaltensänderungen:\n- Der `Retry-After` eines `429 rate_limited` gibt die verbleibende Zeit bis zur\n  Wiederöffnung des Zeitfensters an, nicht mehr dessen volle Dauer: Er kann kürzer sein als\n  bisher.\n- `429 quota_exceeded` trägt einen Header `Retry-After`, in Sekunden bis `resetAt`.\n\n### 1.2.1 — November 2026\n\nKorrekturversion, ohne neue Funktion.\n\nVerhaltensänderungen:\n- Eine Bezeichnung mit einem C1-Steuerzeichen (U+0080 bis U+009F, darunter U+0085), einem\n  Zeilen- oder Absatztrennzeichen (U+2028, U+2029) oder einem Schreibrichtungs-Steuerzeichen\n  (U+202A bis U+202E, U+2066 bis U+2069) wird mit `400 validation_error` abgelehnt, wie\n  Steuerzeichen bereits seit 1.1.0.\n- Die Felder der Visitenkarte (`firstName`, `lastName`, `organization`, `jobTitle`,\n  `website`, Werte von `phones` und `emails`, Felder von `addresses`) lehnen dieselben\n  Zeichen ab wie die Bezeichnung, mit `400 validation_error`. Bisher wurde dort ein\n  Nullzeichen akzeptiert.\n- Die Bezeichnung und diese Felder lehnen auch unsichtbare Zeichen ab: U+200B, U+2060 bis\n  U+2064, U+FEFF, U+180E, U+206A bis U+206F, U+FFF9 bis U+FFFB und die «Tag»-Zeichen\n  (U+E0000 bis U+E007F), ausser in einer Flagge eines Landesteils (zum Beispiel 🏴 Schottland).\n  U+200C, U+200D, Variantenselektoren und kombinierende Zeichen bleiben zulässig.\n- Die Bezeichnung und diese Felder werden in der Normalform NFC gespeichert: Ein zerlegt\n  gesendetes «é» («e» gefolgt vom kombinierenden Akzent) wird zusammengesetzt gespeichert\n  und zurückgegeben.\n- Die `message` eines `400 validation_error` nennt immer das betroffene Feld, zum Beispiel\n  «Champ « emails[1].value » : Adresse email invalide».\n\n### 1.2.0 — November 2026\n\nNeu:\n- **Stil über Vorlagen**: Feld `templateId` bei `POST /qr-codes` und `PATCH /qr-codes/{id}`\n  (Kennung einer Vorlage des Kontos, oder `null` für keinen Stil). Siehe «Stil der QR-Codes\n  und Vorlagen».\n- **Vorlagen des Kontos auflisten** mit `GET /templates`. Recht `QR_READ`.\n- Die Antworten `QrCode` enthalten `templateId` und `style` (wirksamer Stil: Farben, Formen,\n  Vorhandensein eines Logos).\n- Das Bild `GET /qr-codes/{id}/image` trägt den wirksamen Stil des QR-Codes; `?style=none`\n  liefert die Standarddarstellung.\n- Neue Fehlercodes, für diese Funktionen: `403 template_locked` und `422 template_required`\n  (vorgeschriebene Vorlagen, Entreprise), `503 render_busy` (mit `Retry-After`) und\n  `500 internal_error` (Bild).\n\nVerhaltensänderungen:\n- `POST /qr-codes` ohne `templateId` wendet die Standardvorlage des Kontos an, falls es eine\n  hat. Bisher hatte ein über die API erstellter QR-Code nie einen Stil.\n- Ein Feld `style` im Inhalt von `POST /qr-codes` oder `PATCH /qr-codes/{id}`, bisher\n  ignoriert, wird mit `400 validation_error` abgelehnt.\n- Alle `ETag` der Bilder ändern sich einmal, mit der neuen Darstellungs-Engine; das Bild eines\n  QR-Codes ohne Stil ändert sich nicht.\n- Der Parameter `style` von `GET /qr-codes/{id}/image`, bisher ignoriert, akzeptiert nur\n  `none`: Jeder andere Wert und ein wiederholtes `style` werden mit `400 validation_error`\n  abgelehnt.\n- Ein unerwarteter Fehler bei der Bilderzeugung antwortet mit `500 internal_error` und dem\n  gemeinsamen Fehlerinhalt (bisher ein `500` ohne Inhalt).\n- Eine PNG-Erzeugung kann mit `503 render_busy` abgelehnt werden, wenn der Server bereits\n  andere Bilder erzeugt: Versuchen Sie es nach `Retry-After` erneut. Diese Ablehnung wird als\n  eine Erzeugung auf das Limit für PNG-Bilder angerechnet.\n\n### 1.1.1 — Oktober 2026\n\nKorrekturversion, ohne neue Funktion.\n\nVerhaltensänderungen:\n- Ein Inhalt von `POST /qr-codes` oder `PATCH /qr-codes/{id}`, der kein gültiges UTF-8 ist\n  (zum Beispiel in ISO-8859-1 kodierter Text), wird mit `400 validation_error` abgelehnt.\n  Bisher wurden die ungültigen Bytes durch «�» ersetzt und gespeichert.\n- Zwei gleichzeitige `DELETE` auf denselben QR-Code konnten noch `500` antworten, und ein\n  `PATCH` auf einen QR-Code, der gleichzeitig gelöscht wird, konnte `200` antworten, ohne\n  etwas zu speichern, oder `500`: Die Anfrage, die den QR-Code bereits gelöscht vorfindet,\n  antwortet jetzt `404 not_found`, wie in 1.1.0 angekündigt.\n\n### 1.1.0 — Oktober 2026\n\nNeu:\n- **Archivieren und reaktivieren** eines QR-Codes mit `PATCH /qr-codes/{id}` und dem Feld\n  `active` (`false` archiviert, `true` reaktiviert), ohne eine Erstellung zu verbrauchen.\n  Recht `QR_UPDATE`.\n- **Liste filtern, durchsuchen und sortieren** mit `GET /qr-codes`: `active`, `type`, `q`\n  (Bezeichnung, Ziel und Ende der öffentlichen URL), `createdFrom`, `createdBefore`, `sort`.\n  Für die nächste Seite senden Sie dieselben Parameter mit `cursor` erneut.\n- **Erstellungskontingent abrufen** mit `GET /usage`: Erstellungen des Monats, monatliches\n  Kontingent, verbleibende Paketguthaben, Datum der Zurücksetzung. Recht `QR_CREATE`.\n- **Erstellung ohne Duplikat wiederholen** mit dem optionalen Header `Idempotency-Key` bei\n  `POST /qr-codes` (UUID v4 empfohlen, keine Personendaten, 24 Stunden). Drei neue\n  Fehlercodes, nur mit diesem Header zurückgegeben: `422 idempotency_key_mismatch`,\n  `409 idempotency_in_progress`, `410 resource_deleted`.\n\nVerhaltensänderungen:\n- `GET /qr-codes` ohne Parameter sortiert nach Erstellungsdatum absteigend, dann nach `id`\n  (vorher: nur nach `id` absteigend); die Reihenfolge ist in der Praxis dieselbe.\n- `nextCursor` ist jetzt ein opaker Wert. Ein ungültiger Cursor oder ein Cursor,\n  der mit anderen Parametern oder von einem anderen Konto erhalten wurde, wird mit\n  `400 validation_error` abgelehnt. Der frühere Cursor der v1.0 (die `id` des letzten\n  Elements) bleibt während der ganzen v1 gültig, für die Liste ohne Filter und Sortierung.\n- Die Parameter `active`, `type`, `q`, `createdFrom`, `createdBefore` und `sort` der Liste,\n  bisher ignoriert, werden jetzt angewendet und geprüft: Ein ungültiger, leerer oder\n  wiederholter Wert wird mit `400 validation_error` abgelehnt.\n- Ein Feld `active` im Inhalt von `PATCH /qr-codes/{id}`, bisher ignoriert, wird jetzt\n  angewendet.\n- Ein Request-Body grösser als 64 KiB bei `POST /qr-codes` oder `PATCH /qr-codes/{id}` wird\n  mit `413 payload_too_large` abgelehnt (neuer Code; ein normaler Inhalt kommt nicht in die\n  Nähe dieser Grenze).\n- Eine Bezeichnung mit einem Steuerzeichen (Nullzeichen, Tabulator, Zeilenumbruch …) wird mit\n  `400 validation_error` abgelehnt.\n- Alle Antworten tragen `Cache-Control: no-store`, ausser dem Bild eines QR-Codes\n  (`private, no-cache` mit `ETag`).\n- Ein `PATCH` oder `DELETE` auf einen QR-Code, der gleichzeitig von einer anderen Anfrage\n  gelöscht wird, antwortet `404 not_found` statt `500`.\n- Eine fehlerhafte Unicode-Zeichenkette (ein einzelnes Surrogat, zum Beispiel `\\ud800`) im\n  Inhalt von `POST /qr-codes` oder `PATCH /qr-codes/{id}` wird mit `400 validation_error`\n  abgelehnt statt mit einem Fehler `500`.\n\n### 1.0.0 — September 2026\n\nErste öffentliche Version: Erstellen, Abrufen, Auflisten, Ändern und Löschen von QR-Codes\n(Typen `URL` und `VCARD`), Bild als PNG oder SVG, Scan-Statistiken.\n","contact":{"name":"QR Suisse"}},"servers":[{"url":"https://app.qrsuisse.ch/api/v1","description":"Produktion"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"QR Codes","x-displayName":"QR-Codes","description":"Verwaltung der QR-Codes des Kontos, dem der API-Schlüssel gehört (Erstellen, Suchen, Ändern, Archivieren, Löschen), Herunterladen ihres Bildes und Abrufen ihrer Statistiken."},{"name":"Usage","x-displayName":"Erstellungskontingent","description":"Stand des API-Erstellungskontingents des Kontos, dem der API-Schlüssel gehört."},{"name":"Templates","x-displayName":"Stilvorlagen","description":"Stilvorlagen des Kontos, dem der API-Schlüssel gehört, nur lesbar, mit `templateId` auf QR-Codes anzuwenden."}],"paths":{"/qr-codes":{"get":{"operationId":"listQrCodes","tags":["QR Codes"],"summary":"Liste der QR-Codes des Kontos","description":"Erforderliches Recht: **`QR_READ`**. Kein Kontingent (nur Lesen).\n\nOhne Parameter: alle QR-Codes des Kontos, aktive und archivierte, die neuesten zuerst\n(absteigendes Erstellungsdatum, dann `id`).\n\nOptionale, kombinierbare Filter: Status (`active`), Typ (`type`), Suche (`q`),\nErstellungszeitraum (`createdFrom`, `createdBefore`), Sortierung (`sort`). Die Suche\numfasst die Bezeichnung, das Ziel und das Ende der öffentlichen URL (`slug`), nie die\nFelder einer Kontaktkarte. Ein ungültiger Wert eines dieser Parameter wird mit `400\nvalidation_error` abgelehnt (die Meldung nennt den Parameter), nie durch einen\nStandardwert ersetzt: Eine Integration erhält nie, ohne es zu wissen, eine andere Liste\nals die angeforderte. Ein doppelt angegebener oder vorhandener, aber leerer Parameter\n(`?active=`) wird ebenso abgelehnt; einzig ein leerer `cursor` (erste Seite) und `limit`\nsind davon ausgenommen. Ein unbekannter Parametername wird ignoriert.\n\nPaginierung per opakem Cursor (`nextCursor`). Senden Sie für die nächste Seite **dieselben**\nFilter- und Sortierparameter erneut, zusammen mit `cursor`; nur `limit` darf sich ändern.\nEin Cursor, der mit anderen Parametern oder von einem anderen Konto erhalten wurde, wird\nmit `400` abgelehnt. Der Filter `accountId` des Schlüssels bleibt die einzige Grundlage\ndafür, was zurückgegeben wird.\n","parameters":[{"$ref":"#/components/parameters/ActiveFilterParam"},{"$ref":"#/components/parameters/TypeFilterParam"},{"$ref":"#/components/parameters/SearchParam"},{"$ref":"#/components/parameters/CreatedFromParam"},{"$ref":"#/components/parameters/CreatedBeforeParam"},{"$ref":"#/components/parameters/SortParam"},{"$ref":"#/components/parameters/LimitParam"},{"$ref":"#/components/parameters/CursorParam"}],"responses":{"200":{"description":"Ergebnisseite.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCodeListPage"}}}},"400":{"description":"Ungültiger Wert eines Filter-, Sortier- oder Cursor-Parameters, wiederholter Parameter\noder Cursor, der mit anderen Parametern erhalten wurde oder dessen letzter QR-Code\ninzwischen gelöscht oder umbenannt wurde (Sortierung nach Bezeichnung). Die Meldung\nnennt den Parameter, nie seinen Wert.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrPlanRequired"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createQrCode","tags":["QR Codes"],"summary":"Erstellen eines QR-Codes","description":"Erforderliches Recht: **`QR_CREATE`**. Unterliegt dem kommerziellen\nErstellungskontingent (monatliches Kontingent des Plans: 5 mit Pro, 100 mit Entreprise,\ndanach Guthaben der API-Erstellungspakete) — geprüft in derselben Transaktion wie das\nSchreiben, gegen eine gleichzeitige Erstellung knapp an der Grenze. Eine Erstellung\nbleibt gezählt, auch wenn der QR-Code danach gelöscht wird.\n\n`type` wird bei der Erstellung festgelegt (`URL` oder `VCARD`, Diskriminator des\nAnfrageinhalts) und ist danach **nie** änderbar. Bei `VCARD` sind `phones`/`emails`/\n`addresses` jeweils optional — fehlen sie, werden sie als leere Arrays behandelt (eine\nminimale Karte mit Vor- und Nachname ist ein gültiger Anwendungsfall).\n\n**Stil**: `templateId` (optional) wendet eine Stilvorlage des Kontos an; fehlt das Feld,\ndie Standardvorlage des Kontos, falls es eine hat; `null`, kein Stil. Ein Feld `style`\nwird mit `400` abgelehnt. Bei vorgeschriebenen Vorlagen (Entreprise): `templateId: null` →\n`403 template_locked`, weder `templateId` noch Standardvorlage → `422 template_required`.\nSiehe «Stil der QR-Codes und Vorlagen». Eine Ablehnung wird nicht auf das Kontingent\nangerechnet.\n\nWird auf das allgemeine Limit **und** auf das gesonderte Limit `api-create` angerechnet\n(8/min, zusätzlich zum allgemeinen Limit von 30/min).\n\n**Wiederholungen ohne Duplikate.** Mit dem optionalen Header `Idempotency-Key` erzeugt\neine nach einem Netzwerkunterbruch oder einer Zeitüberschreitung erneut gesendete\nAnfrage keinen zweiten QR-Code und verbraucht keine zweite Erstellung des Kontingents.\nVerwenden Sie für jede Erstellung einen neuen Zufallswert (UUID v4 empfohlen) und\nsetzen Sie keine Personendaten ein. Verhalten:\n- gleicher Wert und gleicher Inhalt innerhalb von 24 Stunden: `201` mit dem bereits\n  erstellten QR-Code in seinem aktuellen Zustand und dem Header\n  `Idempotent-Replayed: true`; vom Kontingent wird nichts abgezogen. Diese Wiederholung\n  wird wie eine Erstellung auf die Limits angerechnet;\n- gleicher Wert mit anderem Inhalt: `422 idempotency_key_mismatch`, es wird nichts\n  erstellt;\n- gleicher Wert, während die erste Anfrage noch läuft: Die zweite wartet\n  normalerweise deren Ende ab und erhält denselben QR-Code; dauert das Warten zu lange,\n  `409 idempotency_in_progress` mit `Retry-After`;\n- der mit diesem Wert erstellte QR-Code wurde inzwischen gelöscht:\n  `410 resource_deleted`, es wird nichts neu erstellt;\n- eine abgelehnte Anfrage (`400`, `403`, `429`, Serverfehler) wird nicht festgehalten:\n  Derselbe Wert kann nach Behebung des Problems erneut verwendet werden;\n- nach 24 Stunden ist der Wert vergessen: Eine neue Anfrage mit demselben Wert\n  erstellt einen neuen QR-Code und verbraucht eine Erstellung.\n\nDer Wert von `Idempotency-Key` gilt pro API-Schlüssel: Derselbe Wert mit einem anderen API-Schlüssel\nerstellt einen anderen QR-Code. Gespeichert werden nur nicht umkehrbare Fingerabdrücke des\nWerts und des Inhalts, zusammen mit der Kennung des erstellten QR-Codes; sie werden\nwährend 24 Stunden berücksichtigt.\n","parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCodeCreateInput"}}}},"responses":{"201":{"description":"QR-Code erstellt, oder Erstellung mit demselben `Idempotency-Key` wiederholt.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"Idempotent-Replayed":{"description":"`true`, wenn die Antwort eine bereits mit demselben `Idempotency-Key` erfolgte\nErstellung wiederholt (keine neue Erstellung, kein Kontingent verbraucht). Sonst\nnicht vorhanden.\n","schema":{"type":"string","enum":["true"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCodeApiDto"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrTemplateLocked"},"404":{"description":"Vorlage `templateId` fehlt oder gehört zu einem anderen Konto (nie von einem einfachen Fehlen unterschieden).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}}},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"410":{"$ref":"#/components/responses/ResourceDeleted"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/IdempotencyKeyMismatchOrTemplateRequired"},"429":{"$ref":"#/components/responses/RateLimitedOrQuotaExceeded"}}}},"/qr-codes/{id}":{"get":{"operationId":"getQrCode","tags":["QR Codes"],"summary":"Details eines QR-Codes","description":"Erforderliches Recht: **`QR_READ`**. Kein Kontingent.\n","parameters":[{"$ref":"#/components/parameters/QrCodeIdParam"}],"responses":{"200":{"description":"QR-Code gefunden.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCodeApiDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrPlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"updateQrCode","tags":["QR Codes"],"summary":"Teilweises Bearbeiten eines QR-Codes","description":"Erforderliches Recht: **`QR_UPDATE`**. Kein Erstellungskontingent: Weder eine Bearbeitung\nnoch eine Reaktivierung (`active: true`) verbraucht ein Guthaben.\n\n**Archivieren und Reaktivieren**: `active: false` archiviert den QR-Code — seine\nöffentliche Weiterleitung antwortet dann mit `404` («QR code introuvable ou désactivé.»),\ngenau wie nach dem Archivieren im Dashboard — und `active: true` reaktiviert ihn. `active` muss ein\nJSON-Boolean sein: `\"false\"`, `0` oder `null` werden mit `400 validation_error`\nabgelehnt. Enthält der Body nur `active` und befindet sich der QR-Code bereits im\nverlangten Zustand, lautet die Antwort `200`, ohne dass etwas geschrieben wird\n(`updatedAt` unverändert). `active` lässt sich\nmit den anderen Feldern kombinieren: Der ganze Body wird vor jedem Schreibvorgang\ngeprüft und dann in einem Schritt angewendet — bei einem `400`-Fehler wird nichts\nangewendet, weder die Archivierung noch die anderen Felder.\n\n`type` ist **nie** änderbar (ein Schlüssel `\"type\"` im Anfrageinhalt wird stillschweigend\nignoriert, nie ein Fehler `400`) — es ist immer der bereits gespeicherte Typ, der\nbestimmt, welche Felder des Anfrageinhalts unten tatsächlich gelten. Felder, die den\nbestehenden Typ der Ressource nicht betreffen, werden ebenso stillschweigend ignoriert\n(nie abgelehnt).\n\nBedeutung eines teilweisen PATCH: Ein im JSON-Inhalt fehlendes Feld lässt den aktuellen\nWert unverändert. Bei `organization`/`jobTitle`/`website` (VCARD) löscht ein\n**vorhandenes** Feld mit leerer Zeichenkette den Wert ausdrücklich (auf `null` gesetzt)\n— entscheidend ist sein Vorhandensein im JSON-Inhalt, nicht nur sein Wert. Bei\n`phones`/`emails`/`addresses` (VCARD) ersetzt ein **vorhandenes** Array (auch leer) die\ngesamte Kategorie.\n\n**Stil**: `templateId` wendet den aktuellen Stil einer Vorlage des Kontos an (auch wenn\nder QR-Code bereits mit dieser Vorlage verknüpft ist); `null` entfernt den Stil; fehlt das\nFeld, bleibt der Stil unverändert. Ein Feld `style` wird mit `400` abgelehnt. Bei\nvorgeschriebenen Vorlagen (Entreprise) wird `templateId: null` mit `403 template_locked`\nabgelehnt; ein QR-Code, dessen Stil nicht aus einer Vorlage stammt, bleibt ohne\n`templateId` änderbar. Eine nicht gefundene Vorlage ergibt `404 not_found`, und es wird\nnichts geschrieben, weder der Stil noch die anderen Felder.\n","parameters":[{"$ref":"#/components/parameters/QrCodeIdParam"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCodeUpdateInput"}}}},"responses":{"200":{"description":"QR-Code aktualisiert.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrCodeApiDto"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrTemplateLocked"},"404":{"description":"QR-Code oder Vorlage `templateId` fehlt oder gehört zu einem anderen Konto (nie von einem einfachen Fehlen unterschieden).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/RateLimited"}}},"delete":{"operationId":"deleteQrCode","tags":["QR Codes"],"summary":"Endgültiges Löschen eines QR-Codes","description":"Erforderliches Recht: **`QR_DELETE`**. Kein Kontingent. Eine Löschung gibt die Erstellung\nnicht an das monatliche Kontingent zurück.\n\n**Tatsächliche** Löschung (keine Archivierung): Telefonnummern, E-Mail-Adressen, Adressen\nund Tagesstatistiken des QR-Codes werden mit ihm gelöscht, und sein Slug wird gleichzeitig\ndauerhaft gesperrt — ein bereits mit diesem Slug gedrucktes Medium zeigt nie auf einen\nanderen, später erstellten QR-Code.\n","parameters":[{"$ref":"#/components/parameters/QrCodeIdParam"}],"responses":{"204":{"description":"QR-Code gelöscht, kein Antwortinhalt.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrPlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/qr-codes/{id}/stats":{"get":{"operationId":"getQrCodeStats","tags":["QR Codes"],"summary":"Scan-Statistiken eines QR-Codes","description":"Erforderliches Recht: **`STATS_READ`**. Kein Kontingent.\n\n`dailyStats` kann Lücken haben: Nur Tage mit mindestens einem Scan haben einen Eintrag\n(Tage ohne Scan werden nicht mit 0 aufgefüllt, anders als im Diagramm des Dashboards) —\nwer die API integriert, verarbeitet Rohdaten, keine Darstellung. Zeitfenster begrenzt auf\nden Verlauf der Pläne Pro und Entreprise (15 Monate) — der Zugang\nzur API selbst ist nur mit einem Pro- oder Entreprise-Plan möglich.\n","parameters":[{"$ref":"#/components/parameters/QrCodeIdParam"}],"responses":{"200":{"description":"Statistiken des QR-Codes.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsApiDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrPlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/qr-codes/{id}/image":{"get":{"operationId":"getQrCodeImage","tags":["QR Codes"],"summary":"Bild eines QR-Codes (PNG oder SVG)","description":"Erforderliches Recht: **`QR_READ`**. Kein Kontingent.\n\nGibt die Bilddatei des QR-Codes zurück, auf dem Server mit seinem **wirksamen Stil**\nerstellt (siehe «Stil der QR-Codes und Vorlagen»): dieselbe Zeichnung wie im Dashboard\n(dieselben Module, Formen und Logofläche), Rand von 4 Modulen, Fehlerkorrektur Stufe M,\noder H mit Logo. Ein QR-Code ohne Stil wird schwarz auf weiss dargestellt. `style=none`\nliefert die Standarddarstellung, ohne den Stil des QR-Codes, ausser bei vorgeschriebenen\nVorlagen (`403 template_locked`).\n\nDer codierte Inhalt ist die öffentliche URL des QR-Codes (`publicUrl` von\n`GET /qr-codes/{id}`): auf der eigenen Domain des Kontos, wenn sie aktiv ist, sonst auf\nder Standard-Domain. Das Bild wird auch geliefert, wenn der QR-Code deaktiviert ist\n(`active: false`).\n\nDie Datei wird als Anhang zurückgegeben (`Content-Disposition: attachment`), mit dem\nNamen `qr-suisse-<slug>.png` oder `qr-suisse-<slug>.svg`. Die Antwort enthält ein `ETag`:\nWenn Sie dessen Wert in `If-None-Match` zurücksenden, erhalten Sie `304 Not Modified`\nohne Inhalt, sofern sich das Bild nicht geändert hat (der Aufruf bleibt authentifiziert,\nwird auf das allgemeine Limit angerechnet und protokolliert, aber nicht auf das Limit für\nPNG-Erzeugungen).\n\nRatenbegrenzung: Zusätzlich zum allgemeinen Limit (30 Anfragen / 60 s pro Schlüssel)\nwird jede PNG-Erzeugung auf das gesonderte Limit von **10 Erzeugungen / 60 s pro Konto**\nangerechnet, das alle Schlüssel des Kontos teilen. SVG ist von diesem gesonderten Limit\nnicht betroffen. Es werden nur wenige gestaltete Bilder gleichzeitig erzeugt: darüber\nhinaus `503 render_busy` mit `Retry-After` (die abgelehnte Erzeugung wird auf das\ngesonderte Limit angerechnet).\nDas `ETag` ändert sich insbesondere, wenn die eigene Domain des Kontos aktiviert oder\ndeaktiviert wird, und wenn sich der wirksame Stil ändert (angewendete Vorlage,\nPlanwechsel).\n","parameters":[{"$ref":"#/components/parameters/QrCodeIdParam"},{"name":"format","in":"query","required":false,"description":"Bildformat, in Kleinbuchstaben. Jeder andere Wert ergibt `400 validation_error` (nie\nein stillschweigender Wechsel auf ein anderes Format).\n","schema":{"type":"string","enum":["png","svg"],"default":"png"}},{"name":"size","in":"query","required":false,"description":"Nur PNG: Seitenlänge des Quadrats in Pixeln, Rand inbegriffen, ganze Zahl von 128 bis\n1024 (mindestens 256 für einen QR-Code mit Logo). Ein nicht ganzzahliger Wert oder ein\nWert ausserhalb dieser Grenzen ergibt\n`400 validation_error`. Wird bei SVG ignoriert (Vektorformat, verlustfrei skalierbar\n— das bevorzugte Format für den Grossformatdruck).\n","schema":{"type":"integer","minimum":128,"maximum":1024,"default":1024}},{"name":"If-None-Match","in":"header","required":false,"description":"`ETag`-Wert aus einem früheren Aufruf.","schema":{"type":"string"}},{"name":"style","in":"query","required":false,"description":"`none`: Standarddarstellung (schwarz auf weiss, Quadrate, ohne Logo), ohne den Stil des\nQR-Codes. Fehlt der Parameter: wirksamer Stil des QR-Codes. Jeder andere Wert und ein\nwiederholtes `style` ergeben `400 validation_error`. Bei vorgeschriebenen Vorlagen\n(Entreprise): `403 template_locked`.\n","schema":{"type":"string","enum":["none"]}}],"responses":{"200":{"description":"Bild des QR-Codes.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"ETag":{"description":"Fingerabdruck des Bildes (Format, Grösse, codierter Inhalt und wirksamer Stil).","schema":{"type":"string"}},"Content-Disposition":{"description":"`attachment; filename=\"qr-suisse-<slug>.<png|svg>\"`","schema":{"type":"string"}},"Cache-Control":{"description":"`private, no-cache` — authentifizierte Antwort, nie in einem gemeinsamen Cache gespeichert, bei jeder Verwendung neu validiert.","schema":{"type":"string"}}},"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"image/svg+xml":{"schema":{"type":"string"}}}},"304":{"description":"Das Bild hat sich seit dem in `If-None-Match` übermittelten `ETag` nicht geändert — kein Inhalt.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"ETag":{"description":"Fingerabdruck des Bildes, identisch mit dem übermittelten.","schema":{"type":"string"}}}},"400":{"description":"Ungültiger Parameter `format`, `size` oder `style`, wiederholtes `style`, oder `size` unter 256 für einen QR-Code mit Logo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrTemplateLocked"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"description":"`rate_limited` — allgemeines Limit überschritten (30 Anfragen / 60 s pro\nAPI-Schlüssel), zu viele mit `401` abgelehnte Anfragen von derselben IP-Adresse\n(60 / 60 s oder 300 / 60 s pro IPv6-Netz /48) oder, beim PNG-Format, Limit für\nErzeugungen überschritten (10 Erzeugungen / 60 s pro Konto).\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitedError"}}}},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/RenderBusy"}}}},"/usage":{"get":{"operationId":"getUsage","tags":["Usage"],"summary":"API-Erstellungskontingent des Kontos abrufen","description":"Erforderliches Recht: **`QR_CREATE`**. Kein Kontingent: Diese Abfrage verbraucht keine\nErstellung.\n\nLiefert für das Konto, dem der Schlüssel gehört, dieselben Zahlen, die im selben Moment\nauf der Seite «API-Schlüssel» des Dashboards angezeigt werden: API-Erstellungen im\nlaufenden UTC-Kalendermonat, monatliches Kontingent des Plans, verbleibende Guthaben in\nden API-Erstellungspaketen und Datum, an dem das monatliche Kontingent zurückgesetzt\nwird. Eine erfolgreiche Erstellung erhöht `used` um 1; eine Löschung senkt den Wert\nnicht. Über das monatliche Kontingent hinaus verbraucht jede Erstellung zusätzlich ein\nPaketguthaben.\n\nRichtwerte: Eine gleichzeitige Erstellung mit einem anderen Schlüssel des Kontos kann sie\nschon beim Lesen ungenau machen. Nur `POST /qr-codes` prüft das Kontingent verbindlich\n(siehe «Kommerzielles Erstellungskontingent»).\n","parameters":[],"responses":{"200":{"description":"API-Erstellungskontingent des Kontos.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"Cache-Control":{"description":"`no-store` — die Antwort ändert sich mit jeder Erstellung und wird nie zwischengespeichert.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageApiDto"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrPlanRequired"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/templates":{"get":{"operationId":"listTemplates","tags":["Templates"],"summary":"Stilvorlagen des Kontos auflisten","description":"Erforderliches Recht: **`QR_READ`**. Kein Kontingent (nur Lesen).\n\nAlle Stilvorlagen des Kontos, alphabetisch nach Namen, mit ihrer `id`, die in\n`templateId` übergeben wird (`POST /qr-codes`, `PATCH /qr-codes/{id}`). Vorlagen werden\nnur im Dashboard erstellt, geändert und gelöscht.\n\nKeine Paginierung: Ein Konto hat höchstens 100 Vorlagen. `nextCursor` ist immer `null`.\n\n`style` ist der Stil, den die Vorlage mit dem aktuellen Plan des Kontos ergibt (Formen mit\nPro und Entreprise, Logo mit Entreprise); ein Logo wird nur durch `hasLogo` angezeigt.\n","parameters":[],"responses":{"200":{"description":"Vorlagen des Kontos.","headers":{"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QrTemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOrPlanRequired"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"qrs_live_<apiKeyId>.<secret>","description":"Opakes zweiteiliges Token, **kein JWT**: `qrs_live_<apiKeyId>.<secret>`,\nübermittelt in `Authorization: Bearer <Token>`.\n"}},"parameters":{"QrCodeIdParam":{"name":"id","in":"path","required":true,"description":"Kennung (`cuid`) des QR-Codes.","schema":{"type":"string"}},"LimitParam":{"name":"limit","in":"query","required":false,"description":"Höchstzahl der Elemente pro Seite. Standard 20. Ein fehlender oder nicht ganzzahliger Wert\noder ein Wert ≤ 0 fällt stillschweigend auf den Standard zurück (Komfortparameter, nie ein\nFehler `400`); ein Wert über 100 wird auf 100 begrenzt.\n","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"CursorParam":{"name":"cursor","in":"query","required":false,"description":"Wert von `nextCursor` aus der vorherigen Seite, unverändert mit denselben Filter- und\nSortierparametern zurückzusenden. Opaker Wert: Leiten Sie nichts daraus ab, sein Format\nkann sich ändern. Fehlt bei der ersten Seite. Ein ungültiger Cursor oder ein Cursor, der\nmit anderen Parametern oder von einem anderen Konto erhalten wurde, wird mit `400`\nabgelehnt. Der frühere Cursor der v1.0 (die `id` des letzten Elements) bleibt während der\ngesamten v1 gültig, für die Liste ohne Filter und ohne Sortierung.\n","schema":{"type":"string","maxLength":512}},"ActiveFilterParam":{"name":"active","in":"query","required":false,"description":"`true`: nur aktive QR-Codes; `false`: nur archivierte. Fehlt: beide.\nGenau `true` oder `false`, in Kleinbuchstaben.\n","schema":{"type":"string","enum":["true","false"]}},"TypeFilterParam":{"name":"type","in":"query","required":false,"description":"Ein oder mehrere Typen, durch Kommas getrennt (höchstens 8 Werte, Gross- oder\nKleinschreibung): `url` (alle Links, `type` `URL`), `web` (Webseite), `email` (Ziel\n`mailto:`), `phone` (`tel:`), `sms` (`sms:`), `ftp` (`ftp:`), `calendar`\n(Kalenderereignis), `vcard` (Kontaktkarte). Ein QR-Code wird zurückgegeben, wenn er einem\nder angegebenen Typen entspricht.\n","style":"form","explode":false,"schema":{"type":"array","maxItems":8,"items":{"type":"string","enum":["url","web","email","phone","sms","ftp","calendar","vcard"]}},"example":["vcard","email"]},"SearchParam":{"name":"q","in":"query","required":false,"description":"Suche in der Bezeichnung, im Ziel und im Ende der öffentlichen URL (`slug`), ohne\nUnterscheidung von Gross- und Kleinschreibung und Akzenten («geneve» findet «Genève»).\nNie in den Feldern einer Kontaktkarte. 1 bis 100 Zeichen nach Entfernen der Leerzeichen am\nAnfang und am Ende, ohne Steuerzeichen; `%` und `_` werden wörtlich gesucht.\n","schema":{"type":"string","minLength":1,"maxLength":100}},"CreatedFromParam":{"name":"createdFrom","in":"query","required":false,"description":"QR-Codes, die ab diesem Datum erstellt wurden, einschliesslich. ISO 8601: `JJJJ-MM-TT`\n(Mitternacht UTC) oder Datum mit Uhrzeit und `Z` oder einem Versatz. Kodieren Sie in der\nURL das `+` eines Versatzes als `%2B` (`createdFrom=2026-10-01T08:00:00%2B02:00`) oder\nverwenden Sie `Z` (`createdFrom=2026-10-01T06:00:00Z`); ein nicht kodiertes `+`, das als\nLeerzeichen ankommt, wird unmittelbar vor dem Versatz ebenfalls akzeptiert. Jahr von 1970\nbis 9999, in UTC berechnet, nachdem der Versatz angewendet wurde; ein nicht existierendes\nDatum (`2026-02-30`) oder ein Datum ausserhalb dieses Bereichs wird mit\n`400 validation_error` abgelehnt.\n","schema":{"type":"string"},"example":"2026-10-01"},"CreatedBeforeParam":{"name":"createdBefore","in":"query","required":false,"description":"QR-Codes, die vor diesem Datum erstellt wurden, ausschliesslich (gleiches Format wie\n`createdFrom`). Mit `createdFrom=2026-10-01&createdBefore=2026-11-01`: der ganze Monat\nOktober (UTC). `createdFrom` muss vor `createdBefore` liegen.\n","schema":{"type":"string"},"example":"2026-11-01"},"SortParam":{"name":"sort","in":"query","required":false,"description":"Reihenfolge der Liste: `created_desc` (Standard, neueste zuerst), `created_asc`,\n`label_asc` (Bezeichnung von A bis Z), `label_desc`. Bei Gleichstand entscheidet `id`.\nWird mit `label_asc` oder `label_desc` der letzte QR-Code einer Seite vor dem nächsten\nAufruf gelöscht oder umbenannt, antwortet die nächste Seite mit `400` (beginnen Sie die\nPaginierung wieder bei der ersten Seite), statt stillschweigend QR-Codes auszulassen: Für\neine vollständige Synchronisierung ist `created_desc` oder\n`created_asc` vorzuziehen.\n","schema":{"type":"string","enum":["created_desc","created_asc","label_asc","label_desc"],"default":"created_desc"}},"IdempotencyKeyHeader":{"name":"Idempotency-Key","in":"header","required":false,"description":"Vom Client für eine logische Erstellung gewählter Schlüssel, mit dem dieselbe Anfrage\nohne Duplikat erneut gesendet werden kann (siehe `POST /qr-codes`). 1 bis 255 sichtbare\nASCII-Zeichen, ohne Leerzeichen; umschliessende Anführungszeichen werden ignoriert.\nUUID v4 empfohlen, keine Personendaten. Ein ungültiger Wert ergibt\n`400 validation_error`.\n","schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[\\x21-\\x7E]{1,255}$"},"example":"3f1c2b0e-8a4d-4c6e-9b7a-1d2e3f4a5b6c"}},"headers":{"RateLimitPolicy":{"description":"Regel jedes von der Anfrage abgefragten Limits der Ratenbegrenzung (IETF-Entwurf\n`draft-ietf-httpapi-ratelimit-headers`): Name des Limits, `q` erlaubte Anfragen pro\nZeitfenster, `w` Dauer des Zeitfensters in Sekunden. Fehlt bei einem `401`. Siehe\n«Ratenbegrenzung (Rate Limiting)».\n","schema":{"type":"string","example":"\"api-request\";q=30;w=60, \"api-create\";q=8;w=60"}},"RateLimit":{"description":"Zustand jedes Limits nach der Anfrage, in derselben Reihenfolge wie `RateLimit-Policy`:\n`r` verbleibende Anfragen, `t` Sekunden bis zur Wiederöffnung des Zeitfensters.\n","schema":{"type":"string","example":"\"api-request\";r=29;t=60, \"api-create\";r=7;t=60"}},"RetryAfter":{"description":"Sekunden bis zur Wiederöffnung des Zeitfensters des ausgeschöpften Limits.","schema":{"type":"integer","minimum":1}}},"schemas":{"QrCodeType":{"type":"string","enum":["URL","VCARD"],"description":"Bei der Erstellung festgelegt, danach nie änderbar."},"PhoneType":{"type":"string","enum":["PROFESSIONAL","MOBILE","FAX"]},"EmailType":{"type":"string","enum":["PROFESSIONAL","PERSONAL"]},"AddressType":{"type":"string","enum":["PROFESSIONAL","HOME"]},"PhoneEntry":{"type":"object","required":["type","value"],"properties":{"type":{"$ref":"#/components/schemas/PhoneType"},"value":{"type":"string","minLength":1,"maxLength":255,"description":"Telefonnummer wie eingegeben (kein normiertes Format vorgeschrieben)."}},"description":"Höchstens 3 Telefonnummern pro VCARD-QR-Code."},"EmailEntry":{"type":"object","required":["type","value"],"properties":{"type":{"$ref":"#/components/schemas/EmailType"},"value":{"type":"string","format":"email","maxLength":255}},"description":"Höchstens 2 E-Mail-Adressen pro VCARD-QR-Code."},"AddressEntry":{"type":"object","required":["type"],"properties":{"type":{"$ref":"#/components/schemas/AddressType"},"street":{"type":"string","maxLength":255},"complement":{"type":"string","maxLength":255},"postalCode":{"type":"string","maxLength":10},"city":{"type":"string","maxLength":255}},"description":"Höchstens 2 Postadressen pro VCARD-QR-Code. Die 4 Felder sind\njeweils optional (keines ist für einen gültigen Eintrag erforderlich).\n"},"VCardData":{"type":"object","required":["firstName","lastName","phones","emails","addresses"],"properties":{"firstName":{"type":"string","minLength":1,"maxLength":255},"lastName":{"type":"string","minLength":1,"maxLength":255},"organization":{"type":"string","maxLength":255},"jobTitle":{"type":"string","maxLength":255},"website":{"type":"string","maxLength":255},"phones":{"type":"array","items":{"$ref":"#/components/schemas/PhoneEntry"},"maxItems":3},"emails":{"type":"array","items":{"$ref":"#/components/schemas/EmailEntry"},"maxItems":2},"addresses":{"type":"array","items":{"$ref":"#/components/schemas/AddressEntry"},"maxItems":2}},"description":"Nur in `QrCodeApiDto.vcard` vorhanden, wenn `type === \"VCARD\"`. `organization`/\n`jobTitle`/`website` fehlen in der Antwort, wenn sie nicht ausgefüllt sind (nie ein\nausdrückliches `null`) — wer die API integriert, prüft das Vorhandensein des Schlüssels,\nnicht seinen Wert.\n"},"QrCodeApiDto":{"type":"object","required":["id","type","label","active","scanCount","slug","publicUrl","createdAt","updatedAt","templateId","style"],"properties":{"id":{"type":"string","description":"Kennung (`cuid`) des QR-Codes."},"type":{"$ref":"#/components/schemas/QrCodeType"},"label":{"type":"string"},"active":{"type":"boolean"},"scanCount":{"type":"integer","description":"Gesamtzahl der Scans seit der Erstellung."},"slug":{"type":"string"},"publicUrl":{"type":"string","format":"uri","description":"Vollständige öffentliche Weiterleitungs-URL (eigene Domain des Kontos, wenn sie aktiv\nist, sonst Standard-Domain) — nie so gespeichert, immer aus dem aktuellen Zustand des\nKontos berechnet.\n"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"templateId":{"type":["string","null"],"description":"Stilvorlage, deren Stil der QR-Code erhalten hat (`GET /templates`), `null`, wenn keine."},"style":{"$ref":"#/components/schemas/QrStyle"},"destinationUrl":{"type":"string","format":"uri","description":"Nur vorhanden, wenn `type === \"URL\"`, und nur, wenn ausgefüllt."},"vcard":{"allOf":[{"$ref":"#/components/schemas/VCardData"}],"description":"Nur vorhanden, wenn `type === \"VCARD\"`."}},"example":{"id":"cm2f8k3qz0001xyz9abcd1234","type":"URL","label":"Affiche salle d'attente","active":true,"scanCount":42,"slug":"AbC123_-xy","publicUrl":"https://app.qrsuisse.ch/r/AbC123_-xy","createdAt":"2026-10-01T08:30:00.000Z","updatedAt":"2026-10-12T14:05:00.000Z","templateId":"cm2f9a7tp0003xyz9abcd5678","style":{"foreground":"#0B3D5C","background":"#FFFFFF","moduleShape":"ROUNDED","eyeShape":"ROUNDED","hasLogo":false},"destinationUrl":"https://www.exemple.ch/rendez-vous"},"description":"Öffentliche Darstellung eines QR-Codes: Es werden nur die folgenden Felder\nzurückgegeben, keine internen Daten des Kontos.\n"},"QrCodeListPage":{"type":"object","required":["data","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/QrCodeApiDto"}},"nextCursor":{"type":["string","null"],"description":"Opaker Cursor, in `?cursor` mit denselben Filter- und Sortierparametern zu übergeben,\num die nächste Seite zu erhalten; `null`, wenn dies die letzte Seite ist.\n"}}},"TemplateIdInput":{"type":["string","null"],"maxLength":40,"pattern":"^[a-z0-9]{1,40}$","description":"Kennung einer Stilvorlage des Kontos (`GET /templates`), deren Stil auf den QR-Code\nangewendet wird; `null`: kein Stil. Siehe «Stil der QR-Codes und Vorlagen».\n"},"QrStyle":{"type":"object","required":["foreground","background","moduleShape","eyeShape","hasLogo"],"additionalProperties":false,"properties":{"foreground":{"type":"string","pattern":"^#[0-9A-F]{6}$","description":"Farbe der Module, `#RRGGBB` in Grossbuchstaben."},"background":{"type":"string","pattern":"^#[0-9A-F]{6}$","description":"Farbe des Hintergrunds, Rand inbegriffen, `#RRGGBB` in Grossbuchstaben."},"moduleShape":{"type":"string","enum":["SQUARE","ROUNDED","DOT"],"description":"Form der Module — Quadrate, abgerundet oder Punkte."},"eyeShape":{"type":"string","enum":["SQUARE","ROUNDED","ROUND_CENTER"],"description":"Form der drei Positionsmarken in den Ecken — Quadrate, abgerundet oder runde Mitte."},"hasLogo":{"type":"boolean","description":"`true`, wenn das Bild ein Logo in der Mitte trägt. Das Logo selbst wird von der API nie zurückgegeben."}},"example":{"foreground":"#0B3D5C","background":"#FFFFFF","moduleShape":"ROUNDED","eyeShape":"ROUNDED","hasLogo":false},"description":"Wirksamer Stil: der Stil, den das Bild mit dem aktuellen Plan des Kontos zeichnet (Formen\nmit Pro und Entreprise, Logo mit Entreprise). Ohne Stil: `#000000` auf `#FFFFFF`,\n`SQUARE`, `SQUARE`, ohne Logo. Nur lesbar: Ein Stil wird nie gesendet, nur eine\n`templateId`.\n"},"QrTemplateApiDto":{"type":"object","required":["id","name","isDefault","style"],"properties":{"id":{"type":"string","description":"Kennung der Vorlage, in `templateId` zu übergeben."},"name":{"type":"string","maxLength":60},"isDefault":{"type":"boolean","description":"`true` für die Standardvorlage des Kontos, die bei einer Erstellung ohne `templateId` angewendet wird."},"style":{"$ref":"#/components/schemas/QrStyle"}},"example":{"id":"cm2f9a7tp0003xyz9abcd5678","name":"Affiches","isDefault":true,"style":{"foreground":"#0B3D5C","background":"#FFFFFF","moduleShape":"ROUNDED","eyeShape":"ROUNDED","hasLogo":false}},"description":"Stilvorlage des Kontos, nur lesbar."},"QrTemplateList":{"type":"object","required":["data","nextCursor"],"properties":{"data":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/QrTemplateApiDto"}},"nextCursor":{"type":"null","description":"Immer `null` (keine Paginierung, höchstens 100 Vorlagen)."}}},"QrCodeCreateUrlInput":{"type":"object","required":["type","label","destinationUrl"],"properties":{"type":{"type":"string","enum":["URL"]},"label":{"type":"string","minLength":1,"maxLength":255},"destinationUrl":{"type":"string","maxLength":2048,"description":"Ziel-URL — akzeptierte Protokolle: `http:`/`https:` (ohne weitere Einschränkung),\n`mailto:` (lokaler Teil vor einem allfälligen `?` als E-Mail-Adresse geprüft),\n`tel:`/`sms:` (nur Ziffern/Leerzeichen/`+`/`-`/Klammern, 4 bis 20 Zeichen, `*`/`#`\nausdrücklich verboten — Risiko eines USSD-Codes), `ftp:` (Zugangsdaten im Klartext in\nder Authority verboten). Kein Steuerzeichen (wörtlich oder als `%XX` codiert), kein\nunsichtbares oder irreführendes Zeichen (Zeichen in voller Breite,\nSchreibrichtungs-Steuerzeichen, «Tag»-Zeichen …, siehe Version 1.3.1).\n"},"templateId":{"$ref":"#/components/schemas/TemplateIdInput"}},"example":{"type":"URL","label":"Affiche salle d'attente","destinationUrl":"https://www.exemple.ch/rendez-vous"},"description":"Inhalt von `POST /qr-codes` für einen QR-Code vom Typ `URL`."},"QrCodeCreateVcardInput":{"type":"object","required":["type","label","firstName","lastName"],"properties":{"type":{"type":"string","enum":["VCARD"]},"label":{"type":"string","minLength":1,"maxLength":255},"firstName":{"type":"string","minLength":1,"maxLength":255},"lastName":{"type":"string","minLength":1,"maxLength":255},"organization":{"type":"string","maxLength":255},"jobTitle":{"type":"string","maxLength":255},"website":{"type":"string","maxLength":255},"phones":{"type":"array","items":{"$ref":"#/components/schemas/PhoneEntry"},"maxItems":3,"default":[]},"emails":{"type":"array","items":{"$ref":"#/components/schemas/EmailEntry"},"maxItems":2,"default":[]},"addresses":{"type":"array","items":{"$ref":"#/components/schemas/AddressEntry"},"maxItems":2,"default":[]},"templateId":{"$ref":"#/components/schemas/TemplateIdInput"}},"example":{"type":"VCARD","label":"Carte de visite Dr Muster","firstName":"Anna","lastName":"Muster","organization":"Cabinet Muster","phones":[{"type":"PROFESSIONAL","value":"+41 27 123 45 67"}],"emails":[{"type":"PROFESSIONAL","value":"anna.muster@exemple.ch"}]},"description":"Inhalt von `POST /qr-codes` für einen QR-Code vom Typ `VCARD`. Nur `label`/`firstName`/\n`lastName` sind Pflicht — eine minimale Karte (nur ein Name) ist ein gültiger\nAnwendungsfall; fehlende `phones`/`emails`/`addresses` entsprechen einem leeren Array.\n"},"QrCodeCreateInput":{"oneOf":[{"$ref":"#/components/schemas/QrCodeCreateUrlInput"},{"$ref":"#/components/schemas/QrCodeCreateVcardInput"}],"discriminator":{"propertyName":"type","mapping":{"URL":"#/components/schemas/QrCodeCreateUrlInput","VCARD":"#/components/schemas/QrCodeCreateVcardInput"}},"description":"Nach `type` unterschiedene Union, bei der Erstellung festgelegt — dieselben\nValidierungsregeln wie im Dashboard (unverändert übernommen, kein paralleles Schema).\n"},"QrCodeUpdateInput":{"type":"object","properties":{"active":{"type":"boolean","description":"`false` archiviert den QR-Code, `true` reaktiviert ihn. Strikter JSON-Boolean, gilt für beide Typen."},"label":{"type":"string","minLength":1,"maxLength":255},"destinationUrl":{"type":"string","maxLength":2048,"description":"Gilt nur, wenn die bestehende Ressource vom Typ `URL` ist — siehe Beschreibung der Operation."},"firstName":{"type":"string","minLength":1,"maxLength":255},"lastName":{"type":"string","minLength":1,"maxLength":255},"organization":{"type":"string","maxLength":255,"description":"Eine leere Zeichenkette löscht den Wert ausdrücklich (`null`), sofern das Feld vorhanden ist."},"jobTitle":{"type":"string","maxLength":255,"description":"Eine leere Zeichenkette löscht den Wert ausdrücklich (`null`), sofern das Feld vorhanden ist."},"website":{"type":"string","maxLength":255,"description":"Eine leere Zeichenkette löscht den Wert ausdrücklich (`null`), sofern das Feld vorhanden ist."},"phones":{"type":"array","items":{"$ref":"#/components/schemas/PhoneEntry"},"maxItems":3,"description":"Falls vorhanden (auch leer), ersetzt die gesamte Kategorie."},"emails":{"type":"array","items":{"$ref":"#/components/schemas/EmailEntry"},"maxItems":2,"description":"Falls vorhanden (auch leer), ersetzt die gesamte Kategorie."},"addresses":{"type":"array","items":{"$ref":"#/components/schemas/AddressEntry"},"maxItems":2,"description":"Falls vorhanden (auch leer), ersetzt die gesamte Kategorie."},"templateId":{"$ref":"#/components/schemas/TemplateIdInput"}},"example":{"destinationUrl":"https://www.exemple.ch/nouvelle-page"},"description":"Inhalt von `PATCH /qr-codes/{id}` — **alle Felder sind optional** (teilweise Bearbeitung:\nEin fehlendes Feld lässt den aktuellen Wert unverändert). `type` erscheint **nie** in\ndiesem Inhalt (nicht änderbar, siehe Beschreibung der Operation): Es ist der bereits\ngespeicherte Typ, der bestimmt, welche Felder tatsächlich gelten, `destinationUrl`\n(`URL`) oder `firstName`/`lastName`/`organization`/`jobTitle`/`website`/`phones`/\n`emails`/`addresses` (`VCARD`) — Felder, die den bestehenden Typ nicht betreffen, werden\nstillschweigend ignoriert, nie abgelehnt.\n"},"StatsApiDto":{"type":"object","required":["qrCodeId","scanCountTotal","dailyStats"],"properties":{"qrCodeId":{"type":"string"},"scanCountTotal":{"type":"integer","description":"Gesamtzahl der Scans seit der Erstellung (`QrCode.scanCount`), nicht nur im Zeitfenster von `dailyStats`."},"dailyStats":{"type":"array","items":{"type":"object","required":["date","count"],"properties":{"date":{"type":"string","format":"date-time"},"count":{"type":"integer"}}},"description":"Ein Element pro Tag mit mindestens einem Scan (keine Auffüllung mit 0), begrenzt auf\ndas Verlaufsfenster der Pläne Pro und Entreprise (15 Monate).\n"}},"example":{"qrCodeId":"cm2f8k3qz0001xyz9abcd1234","scanCountTotal":42,"dailyStats":[{"date":"2026-10-11T00:00:00.000Z","count":5},{"date":"2026-10-12T00:00:00.000Z","count":3}]},"description":"Öffentliches DTO des Statistik-Endpoints."},"UsageApiDto":{"type":"object","required":["used","monthlyQuota","packCreditsRemaining","resetAt"],"additionalProperties":false,"properties":{"used":{"type":"integer","minimum":0,"description":"API-Erstellungen im laufenden UTC-Kalendermonat, einschliesslich derjenigen, die ein\nPaketguthaben verbraucht haben, und derjenigen von seither gelöschten QR-Codes. Kann\ndaher `monthlyQuota` übersteigen.\n"},"monthlyQuota":{"type":"integer","minimum":0,"description":"Im Plan monatlich enthaltene Erstellungen (5 mit Pro, 100 mit Entreprise)."},"packCreditsRemaining":{"type":"integer","minimum":0,"description":"Noch verfügbare Guthaben in den API-Erstellungspaketen des Kontos, alle nutzbaren Pakete zusammen."},"resetAt":{"type":"string","format":"date-time","description":"Zurücksetzung des monatlichen Kontingents — 1. Tag des folgenden UTC-Kalendermonats, um Mitternacht UTC."}},"example":{"used":7,"monthlyQuota":5,"packCreditsRemaining":98,"resetAt":"2026-11-01T00:00:00.000Z"},"description":"Öffentliches DTO des Kontingent-Endpoints — nur diese vier Felder."},"ApiErrorBase":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","enum":["unauthorized","plan_required","forbidden","account_disabled","https_required","not_found","method_not_allowed","validation_error","quota_exceeded","rate_limited","idempotency_key_mismatch","idempotency_in_progress","resource_deleted","payload_too_large","template_locked","template_required","render_busy","internal_error"]},"message":{"type":"string"}},"description":"Gemeinsame Form aller Fehler der API v1."},"UnauthorizedError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"unauthorized","message":"Authentification invalide ou absente."}},"PlanRequiredError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"plan_required","message":"Cette fonctionnalité nécessite un plan Pro ou Entreprise."}},"ForbiddenError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"forbidden","message":"Cette clé API n'a pas le droit requis pour cette opération."}},"AccountDisabledError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"account_disabled","message":"Ce compte a été suspendu. Contactez le support QR Suisse à l'adresse contact@qrsuisse.ch pour plus d'informations."},"description":"Konto ausgesetzt oder wartet auf die Löschung. `message` (auf Französisch) lautet je nach\nZustand:\n- ausgesetzt: « Ce compte a été suspendu. Contactez le support QR Suisse à l'adresse\n  contact@qrsuisse.ch pour plus d'informations. » (Dieses Konto wurde ausgesetzt.\n  Kontaktieren Sie den QR-Suisse-Support unter contact@qrsuisse.ch für weitere\n  Informationen.)\n- wartet auf die Löschung: « Ce compte est en attente de suppression pour inactivité.\n  Utilisez le lien de récupération reçu par email pour le réactiver. » (Dieses Konto\n  wartet wegen Inaktivität auf die Löschung. Verwenden Sie den per E-Mail erhaltenen\n  Wiederherstellungslink, um es zu reaktivieren.)\n"},"HttpsRequiredError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"https_required","message":"L'API n'accepte que HTTPS : la requête n'a pas été traitée. Une clé envoyée en HTTP a circulé en clair, faites-la tourner depuis le tableau de bord."},"description":"Über HTTP erhaltene Anfrage: weder umgeleitet noch verarbeitet, noch bevor der Schlüssel\ngeprüft wird. Ein so gesendeter Schlüssel wurde unverschlüsselt übertragen und muss\nrotiert werden.\n"},"NotFoundError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"not_found","message":"Ressource introuvable."}},"ValidationError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"validation_error","message":"Champ « label » : Le libellé est requis"},"description":"`message` übernimmt die erste festgestellte Validierungsmeldung (z. B. ein Pflichtfeld,\ndas fehlt oder zu lang ist) — der genaue Text hängt vom betroffenen Feld ab, die Form\n`{ error, message }` bleibt immer gleich. Dieser Text ist auf Französisch und\nnennt das betroffene Feld (z. B. «Champ « type » requis : valeurs acceptées URL,\nVCARD.»): Nur der Code `error` ist garantiert.\n"},"QuotaExceededError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"},{"type":"object","required":["resetAt"],"properties":{"resetAt":{"type":"string","format":"date-time","description":"1. Tag des folgenden UTC-Kalendermonats, um Mitternacht UTC — Datum, an dem das Kontingent zurückgesetzt wird."}}}],"example":{"error":"quota_exceeded","message":"Quota mensuel de créations API atteint.","resetAt":"2026-10-01T00:00:00.000Z"}},"RateLimitedError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"rate_limited","message":"Trop de requêtes pour cette clé API, réessayez plus tard."}},"IdempotencyKeyMismatchError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"idempotency_key_mismatch","message":"Cette clé Idempotency-Key a déjà servi pour une requête différente. Utilisez une nouvelle clé pour une nouvelle création."}},"IdempotencyInProgressError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"idempotency_in_progress","message":"Une requête avec cette clé Idempotency-Key est encore en cours de traitement, réessayez dans un instant."}},"ResourceDeletedError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"resource_deleted","message":"Le QR code créé avec cette clé Idempotency-Key a été supprimé depuis. Utilisez une nouvelle clé pour une nouvelle création."}},"PayloadTooLargeError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"payload_too_large","message":"Corps de la requête trop volumineux : 64 Kio maximum."}},"TemplateLockedError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"template_locked","message":"Les modèles sont imposés sur ce compte : « templateId » ne peut pas valoir null (voir GET /templates)."},"description":"Für das Konto vorgeschriebene Vorlagen (Entreprise). `message` (auf Französisch) lautet je\nnach Fall:\n- `POST /qr-codes` oder `PATCH /qr-codes/{id}` mit `templateId: null`: « Les modèles sont imposés sur ce compte : « templateId » ne peut pas valoir null (voir GET /templates). »\n  (Die Vorlagen sind für dieses Konto vorgeschrieben: «templateId» darf nicht null sein.)\n- `GET /qr-codes/{id}/image?style=none`: « Les modèles sont imposés sur ce compte : l'image sans style (style=none) n'est pas disponible. » (Die Vorlagen sind für dieses\n  Konto vorgeschrieben: Das Bild ohne Stil ist nicht verfügbar.)\n"},"TemplateRequiredError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"template_required","message":"Les modèles sont imposés sur ce compte et aucun modèle par défaut n'est défini : indiquez « templateId » (voir GET /templates)."}},"RenderBusyError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"render_busy","message":"Le serveur prépare déjà d'autres images, réessayez dans un instant."}},"InternalError":{"allOf":[{"$ref":"#/components/schemas/ApiErrorBase"}],"example":{"error":"internal_error","message":"L'image n'a pas pu être générée. Réessayez plus tard ou contactez le support."}}},"responses":{"Unauthorized":{"description":"Header `Authorization` fehlt oder ist fehlerhaft, Schlüssel unbekannt/widerrufen/eingefroren oder Geheimnis falsch.","headers":{"WWW-Authenticate":{"description":"Immer `Bearer realm=\"qrsuisse-api\"`, unabhängig vom Grund der Ablehnung.","schema":{"type":"string","const":"Bearer realm=\"qrsuisse-api\""}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedError"}}}},"ForbiddenOrPlanRequired":{"description":"`403` — entweder wurde die Anfrage über HTTP gesendet (`https_required`, noch bevor der\nSchlüssel geprüft wird), oder das Konto ist ausgesetzt oder wartet auf die Löschung\n(`account_disabled`, bei einem gültigen Schlüssel zuerst geprüft), oder das Konto hat\nkeinen Pro- oder Entreprise-Plan mehr (`plan_required`), oder der authentifizierte\nAPI-Schlüssel hat nicht das für diese Operation erforderliche atomare Recht\n(`forbidden`).\n","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/HttpsRequiredError"},{"$ref":"#/components/schemas/AccountDisabledError"},{"$ref":"#/components/schemas/PlanRequiredError"},{"$ref":"#/components/schemas/ForbiddenError"}]}}}},"ForbiddenOrTemplateLocked":{"description":"`403` — dieselben Ablehnungen wie bei jeder Operation (`https_required`,\n`account_disabled`, `plan_required`, `forbidden`), oder die vorgeschriebenen Vorlagen des\nKontos (Entreprise) lehnen die Anfrage ab (`template_locked`, siehe «Stil der QR-Codes und\nVorlagen»).\n","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/HttpsRequiredError"},{"$ref":"#/components/schemas/AccountDisabledError"},{"$ref":"#/components/schemas/PlanRequiredError"},{"$ref":"#/components/schemas/ForbiddenError"},{"$ref":"#/components/schemas/TemplateLockedError"}]}}}},"NotFound":{"description":"Ressource fehlt oder gehört zu einem anderen Konto (nie von einem einfachen Fehlen unterschieden).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFoundError"}}}},"ValidationError":{"description":"Anfrageinhalt fehlt, ist kein JSON oder entspricht nicht den Validierungsregeln des betroffenen Felds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"RateLimited":{"description":"Allgemeines Limit überschritten (30 Anfragen / 60 s pro API-Schlüssel) oder zu viele\nmit `401` abgelehnte Anfragen von derselben IP-Adresse (60 / 60 s oder 300 / 60 s pro\nIPv6-Netz /48).\n","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitedError"}}}},"RateLimitedOrQuotaExceeded":{"description":"`429` — entweder ein Limit der Ratenbegrenzung überschritten (allgemein 30/min,\nErstellung 8/min oder 60 `401`-Ablehnungen/min von derselben IP-Adresse, 300/min pro\nIPv6-Netz /48 — `rate_limited`), oder das kommerzielle monatliche Kontingent an\nAPI-Erstellungen erreicht (`quota_exceeded`, noch vor jedem Schreiben in die Datenbank).\n","headers":{"Retry-After":{"description":"Sekunden bis zum nächsten Versuch: Wiederöffnung des Zeitfensters des ausgeschöpften\nLimits bei `rate_limited`, verbleibende Zeit bis `resetAt` bei `quota_exceeded`.\n","schema":{"type":"integer","minimum":1}},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/RateLimitedError"},{"$ref":"#/components/schemas/QuotaExceededError"}]}}}},"IdempotencyKeyMismatch":{"description":"`422` — derselbe `Idempotency-Key` wurde innerhalb der letzten 24 Stunden bereits für eine\nAnfrage mit anderem Inhalt verwendet. Es wird nichts erstellt, kein Kontingent verbraucht.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdempotencyKeyMismatchError"}}}},"IdempotencyKeyMismatchOrTemplateRequired":{"description":"`422` — entweder wurde derselbe `Idempotency-Key` innerhalb der letzten 24 Stunden bereits\nfür eine Anfrage mit anderem Inhalt verwendet (`idempotency_key_mismatch`), oder die\nVorlagen sind für das Konto vorgeschrieben und das Konto hat keine Standardvorlage\n(`template_required`). Es wird nichts erstellt, kein Kontingent verbraucht.\n","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/IdempotencyKeyMismatchError"},{"$ref":"#/components/schemas/TemplateRequiredError"}]}}}},"IdempotencyInProgress":{"description":"`409` — eine Anfrage mit demselben `Idempotency-Key` läuft noch, und das Warten auf ihr\nErgebnis hat zu lange gedauert. Diese Anfrage erstellt nichts: Senden Sie sie nach\n`Retry-After` erneut, sie erhält den von der ersten erstellten QR-Code.\n","headers":{"Retry-After":{"description":"Empfohlene Wartezeit vor einem neuen Versuch, in Sekunden.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdempotencyInProgressError"}}}},"ResourceDeleted":{"description":"`410` — der mit diesem `Idempotency-Key` erstellte QR-Code wurde inzwischen gelöscht. Es\nwird nichts neu erstellt: Verwenden Sie für eine neue Erstellung einen neuen Schlüssel.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceDeletedError"}}}},"PayloadTooLarge":{"description":"`413` — Request-Body grösser als 64 KiB (65 536 Byte): Die Anfrage wird abgelehnt, es wird\nnichts geschrieben.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayloadTooLargeError"}}}},"RenderBusy":{"description":"`503` — der Server erzeugt bereits andere gestaltete Bilder. Nichts wird in eine\nWarteschlange gestellt: Versuchen Sie es nach `Retry-After` erneut. Die abgelehnte\nErzeugung wird auf das Limit für PNG-Bilder angerechnet.\n","headers":{"Retry-After":{"description":"Empfohlene Wartezeit vor einem neuen Versuch, in Sekunden.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderBusyError"}}}},"InternalError":{"description":"`500` — das Bild konnte nicht erzeugt werden. Der Inhalt nennt kein technisches Detail;\nversuchen Sie es später erneut und wenden Sie sich an den Support, wenn der Fehler\nbestehen bleibt.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InternalError"}}}}}}}