{
  "openapi": "3.1.0",
  "info": {
    "title": "Wisnots Netzanmeldung API",
    "version": "0.1.0-draft",
    "summary": "Eine Anlage beim zuständigen deutschen Netzbetreiber anmelden, welcher es auch ist.",
    "description": "## Worum es geht\n\nEine Anlage beim zuständigen deutschen Netzbetreiber anmelden: Photovoltaik, eine Wärmepumpe, eine Wallbox, und mit ihnen die Anmeldung nach § 14a EnWG als steuerbare Verbrauchseinrichtung, wo immer sie greift.\n\n## Eine Route pro Anlagentyp, ein Fallmodell dahinter\n\n`POST /v1/anmeldungen/{typ}` benennt, was Sie anmelden: `pv`, `speicher`, `waermepumpe`, `ladeeinrichtung`, `klimaanlage`.\n\nDer Pfad folgt der Achse des nationalen Datensatzes, den Anlagentypen, nicht dem rechtlichen Regime. § 14a erfasst vier dieser Typen und nicht PV, und seine Schwelle ist eine Zahl in einem Gesetz, das schon einmal umgeschrieben wurde; eine Route pro Typ hält Ihre URL also über eine Änderung des Regimes hinweg stabil. Der Pfad legt zugleich `device.category` fest: Sie senden es nicht und können es nicht falsch machen, und `GET /v1/anmeldungen/{typ}/fields` daneben kommt bereits auf diesen Typ eingegrenzt zurück.\n\n`POST /v1/anmeldungen/14a` bleibt bestehen, für einen Fall, der eröffnet wird, bevor jemand entschieden hat, um welches Gerät es geht. Es ist der eine Erstellpfad, der keine Kategorie festlegt; dort deklarieren Sie sie selbst.\n\nWas jeder von ihnen erzeugt, ist eine gewöhnliche Anmeldung. Alles danach, Lesen, Korrigieren, Material nachreichen, Einreichen, das Ereignisprotokoll, geschieht auf `/v1/anmeldungen/{id}` und ist für jeden Fall identisch, gleich was ihn erzeugt hat. Einen Anlagentyp später hinzuzunehmen ist daher ein neuer Erstellaufruf und sonst nichts auf dieser Seite.\n\nFall-IDs tragen immer ein Präfix, `anm_` live und `anm_test_` in der Sandbox. Das hält ein statisches Typsegment gegenüber `{id}` eindeutig, und es lässt einen Schlüssel, der auf die falsche Umgebung zeigt, an der ID scheitern statt drei Bildschirme tiefer.\n\n## Zwei Wege, uns den Fall zu geben, und sie lassen sich kombinieren\n\n**Alles ablegen.** Senden Sie `material`: eingefügten Text, Kundenmail, PDFs, Fotos. Wir lesen es und geben die gefundenen Felder zurück, jedes mit dem Zitat, aus dem es stammt.\n\n**Alles deklarieren.** Senden Sie `declared`: einen strukturierten Body mit so viel oder so wenig, wie Sie vorliegen haben. Für nichts, was Sie angegeben haben, läuft eine Erkennung.\n\n**Oder beides**, der gewöhnliche Fall. Ein deklarierter Wert ist maßgeblich und wird von der Erkennung nie überschrieben. Wo die Erkennung etwas von Ihnen Deklariertem widerspricht, oder zwei Dokumente einander widersprechen, wird daraus ein Eintrag in `conflicts` statt einer stillen Entscheidung, denn eine Falschangabe gegenüber einem Netzbetreiber ist schlimmer als eine Rückfrage.\n\n## Ein Dokument senden\n\nDateien gehen an `POST /v1/anmeldungen/{typ}/upload`, den Zwilling der Erstellroute, als `multipart/form-data`: ein `file`-Part pro Dokument, `text` für eine eingefügte Mail, und die Felder des JSON-Bodys (`external_ref`, `intent`, `operator`, und `declared` als JSON-Text) als weitere Parts. Zwei Dateien und die Kundenmail, ein Aufruf, und die Antwort ist der neue Fall mit allen dreien unter `material[]`, bereits im Lesen:\n\n```\ncurl https://netz-api.wisnots.com/v1/anmeldungen/pv/upload \\\n  -H \"Authorization: Bearer $NETZ_API_KEY\" \\\n  -F file=@rechnung.pdf -F file=@typenschild.jpg \\\n  -F text=<kundenmail.txt -F external_ref=ERP-9001\n```\n\nDieselben Parts auf `POST /v1/anmeldungen/{id}/upload` ergänzen einen Fall, der bereits existiert:\n\n```\ncurl https://netz-api.wisnots.com/v1/anmeldungen/{id}/upload \\\n  -H \"Authorization: Bearer $NETZ_API_KEY\" \\\n  -F file=@nachtrag.pdf\n```\n\nBis zu 25 MB pro Datei. Was eine Datei ist, ergibt sich aus ihren Bytes; welchen Medientyp curl für den Part rät, spielt also keine Rolle. Eine Datei, die kein PDF, kein JPEG, kein PNG, kein WebP und kein HEIC-Bild ist, ist ein 415, der sie benennt, und von dieser Anfrage wird nichts gespeichert. Auf dieser Seite öffnet *Dateien hochladen* unter jedem Anlagentyp den Client mit diesem Formular und einer Dateiauswahl, sodass die Sandbox von hier aus Ihre eigenen Dokumente liest. Das SDK sendet es mit `cases.createFromFiles` und `cases.upload`.\n\n**In JSON.** Eine Datei reist auch im JSON-Body, base64 in `data`, bis zu 8 MB davon pro Datei, für einen Client, der ohnehin einen JSON-Body baut und lieber kein Formular zusammensetzt:\n\n```\ncurl https://netz-api.wisnots.com/v1/anmeldungen/pv \\\n  -H \"Authorization: Bearer $NETZ_API_KEY\" -H \"Content-Type: application/json\" \\\n  -d \"{\\\"intent\\\":\\\"draft\\\",\\\"material\\\":[{\\\"kind\\\":\\\"file\\\",\\\"filename\\\":\\\"rechnung.pdf\\\",\\\"content_type\\\":\\\"application/pdf\\\",\\\"data\\\":\\\"$(base64 < rechnung.pdf | tr -d '\\n')\\\"}]}\"\n```\n\n**Über ein Upload-Ziel,** für einen Client, der Bytes streamt und die Datei nie in der Hand hält. Drei Aufrufe: den Fall um ein Ziel bitten, die rohen Bytes per POST dorthin senden, die Speicher-ID zurückgeben, mit der es antwortet.\n\n```\ncurl -X POST https://netz-api.wisnots.com/v1/anmeldungen/{id}/documents \\\n  -H \"Authorization: Bearer $NETZ_API_KEY\"\n# {\"upload_url\":\"https://...token=...\"}\n\ncurl -X POST \"<upload_url>\" -H \"Content-Type: application/pdf\" --data-binary @rechnung.pdf\n# {\"storageId\":\"kg2...\"}\n\ncurl -X POST https://netz-api.wisnots.com/v1/anmeldungen/{id}/material \\\n  -H \"Authorization: Bearer $NETZ_API_KEY\" -H \"Content-Type: application/json\" \\\n  -d '{\"material\":[{\"kind\":\"file\",\"storage_id\":\"kg2...\",\"filename\":\"rechnung.pdf\",\"content_type\":\"application/pdf\"}]}'\n```\n\nDer POST an das Ziel trägt die Bytes als Body, kein Formular, ohne Bearer: das Ziel ist die Berechtigung, einmal verwendbar, und läuft innerhalb einer Stunde ab. Das SDK macht die drei Aufrufe mit `cases.uploadByTarget` für Sie.\n\n**Text.** Eine eingefügte Mail oder eine Notiz geht als `text`-Part eines Formulars, oder als `{\"kind\":\"text\",\"label\":\"Kundenmail\",\"content\":\"...\"}` in JSON, ohne Kodierung.\n\n**Dann auf den Fall warten, nicht auf eine Datei.** Material durchläuft `pending`, `reading`, dann `read` oder `unreadable`, und der `status` des Falls ist `extracting`, solange irgendetwas in den ersten beiden steckt. Reagieren Sie auf `case.extraction_completed` aus einem Webhook, oder fragen Sie `GET /v1/anmeldungen/{id}` ab, bis `status` `extracting` verlässt; `cases.waitForExtraction` im SDK tut genau das. Rechnen Sie mit etwa einer Minute für ein einseitiges PDF, mehr für einen Scan oder ein langes.\n\n**Bis zur Einreichung kann nachgereicht werden,** in jedem Status, `ready` eingeschlossen: es wird auf dieselbe Weise gelesen und der Fall wird neu aufgelöst. Nach der Einreichung antworten `/upload` und `/material` mit 409. Ein echtes Dokument ist ein Modellaufruf, weshalb die Sandbox 25 pro Konto und Tag liest; Fixtures sind unbegrenzt, live gibt es keine Obergrenze.\n\n## Was zurückkommt\n\n- `fields`, alles Aufgelöste, jedes mit `source` und, wenn ausgelesen, dem `evidence`-Zitat dahinter.\n- `open`, was noch fehlt, im Wortlaut des Datensatzes, den Worten der Netzbetreiber, jeder Eintrag markiert mit dem, von dem die Antwort erwartet wird, und ob er die Einreichung blockiert.\n- `conflicts`, wo Quellen einander widersprechen.\n- `notices`, Wissenswertes, das nichts blockiert.\n\nEine teilweise Erfassung ist gewollt. Ein Fall mit nicht blockierenden Lücken wird trotzdem eingereicht; den Rest fragen wir danach ab.\n\n## Feldnamen\n\nFelder haben lesbare Pfade wie `site.street`, und jedes trägt seine Nummer im nationalen Datensatz für den Netzanschluss (Version 3.0), dem Feldbestand, auf dem das Portal jedes deutschen Netzbetreibers aufbaut. Was das benannte Schema noch nicht abdeckt, ist über `declared.nr` per Nummer erreichbar; kein Feld des Standards ist je außer Reichweite.\n\n## Die Sandbox\n\nEin `wnz_test_`-Schlüssel statt eines `wnz_live_`-Schlüssels, das ist der ganze Schalter. **Die Sandbox unterscheidet sich von Live an genau einer Stelle: dem unumkehrbaren Akt nach außen.** Die Erkennung ist echt, die Auflösung ist echt, die Anforderungen des Netzbetreibers sind echt, und das PDF ist die echte Vorlage; was Sie gegen die Sandbox bauen, ist, was live passiert.\n\nWas sich unterscheidet:\n\n- **An einen Netzbetreiber wird nie etwas gesendet.** Stattdessen wählen Sie das Ergebnis, mit `sandbox_outcome` auf `POST /{id}/file`, und wie schnell es eintrifft mit `sandbox_pace`: `instant` innerhalb des Aufrufs, oder `staged`, um `submitted`, `waiting_operator` und die Antwort im Abstand weniger Sekunden eintreffen zu sehen, jede als Webhook.\n- **Sie sind der Kunde.** Ein Sandbox-Fall trägt `signature.link`, den Link, den die Signatur-E-Mail verschickt hätte; öffnen Sie ihn, sehen Sie das mit Wasserzeichen versehene Formular, das Ihr Kunde sehen würde, und unterschreiben Sie es, um den Rest der Sequenz zu durchlaufen.\n- **Keine Credits.** Ein Sandbox-Fall verbraucht nichts und wird auch nicht gegen Ihr Guthaben geprüft.\n- **Jedes PDF trägt ein Wasserzeichen** auf jeder Seite. Ein Sandbox-Formular ist ansonsten Byte für Byte plausibel, und irgendwann druckt jemand eines aus.\n- **Fixtures.** `{\"fixture\": \"wallbox-invoice-de\"}` als Material liefert ein bekanntes Leseergebnis ohne Modellaufruf, jedes Mal identisch, sodass ein Bildschirm im Bau beliebig oft neu gebaut werden kann. Es gibt ein Fixture pro Anlagentyp: `wallbox-invoice-de`, `heat-pump-invoice-de`, `pv-invoice-de`, `battery-invoice-de`, `air-conditioning-invoice-de`. Echte Uploads laufen weiterhin durch die echte Erkennung, begrenzt auf 25 Dokumente pro Konto und Tag; darüber ist ein 429, der die Grenze benennt, nie ein stiller Platzhalter.\n- **Dreißig Tage.** Sandbox-Fälle und alles darunter werden nach 30 Tagen gelöscht. Integratoren fügen echte Kunden-PDFs in Sandboxes ein; deshalb wird Ihres nicht mehr da sein.\n- **Ein Sandbox-Netzbetreiber.** `\"operator\": \"sandbox\"` ist fiktiv und sein Formular ändert sich nie, sodass Ihre Tests nicht an dem Tag brechen, an dem ein echter Netzbetreiber seines neu herausgibt.\n\nDie beiden Umgebungen vermischen sich nie. Ein Schlüssel der einen kann einen Fall der anderen weder lesen noch ändern, einreichen oder verwerfen, und eine Seite von `GET /v1/anmeldungen` enthält eine Umgebung. Es gibt keine Beförderung: ein Testfall wird nie ein Live-Fall.\n\n## Schnellstart\n\n1. Melden Sie sich unter https://www.wisnots.com/netzanmeldung/portal an, wechseln Sie auf Sandbox und erstellen Sie unter Entwickler einen Schlüssel. Er beginnt mit `wnz_test_`.\n2. `POST /v1/anmeldungen/ladeeinrichtung` mit `{\"intent\":\"draft\",\"material\":[{\"fixture\":\"wallbox-invoice-de\"}]}`. Die Antwort ist ein Fall mit `status: \"needs_input\"` und `open[]`, das benennt, was noch fehlt, jeder Eintrag mit dem, von dem die Antwort erwartet wird.\n3. `PATCH /v1/anmeldungen/{id}` mit `{\"declared\": {...}}` für die blockierenden Einträge von `open[]`. Der Fall kommt als `ready` zurück.\n4. `POST /v1/anmeldungen/{id}/file` mit `{\"sandbox_outcome\":\"accepted\",\"sandbox_pace\":\"staged\"}`.\n5. Registrieren Sie einen Webhook mit `POST /v1/webhooks`, oder fragen Sie `GET /v1/anmeldungen/{id}/events` ab, und sehen Sie `case.status_changed` dreimal eintreffen: `submitted`, `waiting_operator`, dann die Antwort.\n6. Wechseln Sie auf Live, erstellen Sie einen `wnz_live_`-Schlüssel und senden Sie dieselben Aufrufe ohne die Sandbox-Felder. Sonst ändert sich nichts: nicht die Basis-URL, nicht die Form der IDs, nicht die Antworten.\n\n## Für Ihren Agenten\n\nKopieren Sie den Prompt hinter *Agenten einrichten*, oben auf dieser Seite und in der Konsole unter Entwickler, in einen beliebigen Coding-Agenten. Er verweist den Agenten auf https://www.wisnots.com/netz-api/llms.txt, den Skill unter https://www.wisnots.com/netz-api/skills/wisnots-netz/SKILL.md und dieses OpenAPI-Dokument, und weist ihn an, zuerst gegen die Sandbox zu bauen und Sie nach einem Schlüssel zu fragen, statt einen zu erfinden.\n\n## Ein Fall, ein Anlagentyp\n\nEin Fall meldet einen Anlagentyp des nationalen Datensatzes an. Ein Dach mit PV und einer Batterie sind zwei Fälle, `pv` und `speicher`, so zählen auch der Standard und die Netzbetreiber. Geben Sie beiden dieselbe `external_ref`, und sie bleiben in Ihrem System und in der Konsole beieinander. Mehrere Einheiten eines Typs an einem Anschluss sind ein Fall mit `device.count`, oder `inverters[]`, wo der Standard pro Einheit fragt.\n\n## Für viele Installationsbetriebe anmelden\n\nEine Plattform, die unter einem Schlüssel für viele Installationsbetriebe anmeldet, nennt auf jedem Fall mit `installer_ref` den Betrieb, mit ihrer eigenen festen Kennung für ihn. Jeder Betrieb hat bei jedem Netzbetreiber seinen eigenen Zugang, und ein Fall wird dort mit dem Zugang seines Betriebs angemeldet und mit keinem anderen.\n\nSobald ein Fall seinen Betrieb nennt, trägt `operator_login` den Link des Betriebs: eine Seite, auf der dieser Betrieb Netzbetreiber, Login und Passwort verschlüsselt übermittelt, so viele er hat. Es ist derselbe Link für jeden Fall dieses Betriebs. Senden Sie ihn, wenn `operator_login.status` `missing` oder `unusable` ist; genau dann kommt auch `case.operator_login_missing`, und die Konsole zeigt ihn am Fall. Ein übermittelter Zugang ersetzt nie einen früheren, und wir können einen Link widerrufen, ohne zu verlieren, was über ihn kam. Der Betrieb braucht kein Konto und gibt keine Einwilligung: die Einreichung des Falls ist die Einwilligung.\n\n## Grenzen\n\n- Material: höchstens 30 Materialstücke pro Aufruf und 30 Dateien pro Fall. Ein Multipart-`file` bis 25 MB, inline `data` bis 8 MB base64 pro Datei, Text bis 100.000 Zeichen.\n- Lesen: ein Foto über der Grenze der Erkennung wird verkleinert; ein PDF darüber wird in Abschnitten von wenigen Seiten gelesen, bis zu 60 Seiten. Nur eine einzelne Seite, die allein die Grenze überschreitet, bleibt unlesbar, und das Material sagt es.\n- Declared: 256 KB pro Anfrage, 12 Ebenen tief.\n- Rate: 60 Anfragen im Schub pro Schlüssel, nachgefüllt mit einer pro Sekunde. Eine Ablehnung ist ein 429 mit `Retry-After`.\n- Sandbox: 25 echte Dokumente pro Konto und Tag gelesen, Fixtures unbegrenzt; jeder Sandbox-Fall und alles darunter nach 30 Tagen gelöscht.\n- Das Anfrageprotokoll, das Sie in der Konsole sehen, behält 30 Tage in der Sandbox und 90 Tage live, und nie die Werte eines Live-Falls.\n\n## Versionierung\n\nDer Pfad trägt `/v1`. Innerhalb von v1 ergänzen wir Felder, Ereignistypen und Enum-Werte ohne Ankündigung und benennen nie eines um oder entfernen es; eine Umbenennung oder Entfernung ist ein `/v2`, und v1 funktioniert weiter. Unbekannte Anfragefelder werden abgelehnt, sodass ein Client ein neues Feld erst trifft, wenn er danach fragt, und ein Tippfehler nie still durchgeht. Jede Änderung steht unten.\n\n## Änderungen\n\n- 2026-10-12: `installer_ref` beim Anlegen und bei `PATCH`, und `operator_login` am Fall: eine Plattform, die für viele Installationsbetriebe anmeldet, nennt für jeden Fall den Betrieb, und jeder Betrieb übermittelt seine eigenen Zugänge bei Netzbetreibern über seinen Link. `case.operator_login_missing` sagt, wann einer fehlt.\n- 2026-10-12: `section_14a.consent` ist optional. Die Einreichung des Falls ist die Einwilligung, und `channel` nimmt Ihre eigenen Worte.\n- 2026-10-03: Der Standard für ein fehlendes `connection.planned_commissioning_at` liegt nie in der Vergangenheit. Ist der Tag 14 Tage nach der Einreichung bei uns bei der Vorbereitung der Anmeldung schon vorbei, wird der Fall beim Netzbetreiber mit dem Tag 14 Tage nach dieser Vorbereitung angemeldet. Ein angegebenes Datum wird weiterhin so angemeldet.\n- 2026-10-02: `connection.planned_commissioning_at` ist optional, mit einem angegebenen Standard. Fehlt es, wird der Fall beim Netzbetreiber mit dem Tag 14 Tage nach der Einreichung bei uns angemeldet, nach Berliner Kalender; wer einen Fall ohne es einreicht, stimmt dem zu. Ein angegebenes Datum wird so angemeldet.\n- 2026-09-29: `declarations` auf `POST /{id}/file`. Unter der Fassung der AGB vom 28.09.2026 gibt eine Übergabe die Standarderklärungen des Netzbetreibers für ihren Fall frei; `\"withhold\"` hält sie für einen einzelnen Fall offen.\n- 2026-09-08: Materialzusammenfassungen sind, was das Schema immer beschrieben hat: `kind` ist `file`, `text` oder `fixture` (eine Datei kam bisher als `document` zurück), eine Datei trägt `filename`, `content_type` und `bytes` (bisher nur `label`), ein Fixture trägt `fixture`. `reading` ist ein dokumentierter Status, und der Fall ist `extracting`, solange irgendein Material `pending` oder `reading` ist; zuvor zählte ein Material im Lesen nicht mehr, sodass eine Abfrage von `status` einen Schritt zu früh zurückkommen konnte.\n- 2026-09-07: `POST /v1/anmeldungen/{typ}/upload` neben jeder Erstellroute, und `POST /{id}/upload` auf dem Fall, nehmen Dateien als `multipart/form-data`. `curl -F file=@rechnung.pdf` eröffnet einen Fall mit der Datei darauf; was eine Datei ist, ergibt sich aus ihren Bytes, nicht aus dem Medientyp des Parts.\n- 2026-09-07: die Nummer eines Felds heißt überall `nr` (`fields[].nr`, `open[].nr`, `conflicts[].nr`, die Zeilen von `/fields`), die Hintertür ist `declared.nr`, ein unbenanntes Feld ist `nr.NNNN`, und die Version des Wörterbuchs ist `3.0`. Zuvor hatte der Schlüssel einen anderen Namen; keine Integration war dagegen ausgeliefert.\n- 2026-09-07: `sandbox_pace`; `signature.link` auf Sandbox-Fällen; der `status` eines eingereichten Falls folgt der Antwort des Netzbetreibers (`confirmed`, `rejected`); `GET /v1/netzbetreiber`; die benannten Blöcke `billing`, `builder` und `feed_in` sowie der Rest von `end_customer` und `connection`; ein Hilfetext auf jedem Feld; PDFs über der Grenze der Erkennung seitenweise gelesen; ein Fixture für jeden Anlagentyp.\n- 2026-09-07: Webhooks, und ein Ereignisprotokoll, das den Lebenszyklus der Erkennung trägt.\n- 2026-09-06: eine Route pro Anlagentyp, alle fünf Typen des Standards.\n\n## Webhooks\n\nRegistrieren Sie einen Endpunkt mit `POST /v1/webhooks`, und wir liefern jedes Ereignis Ihrer Umgebung signiert dorthin. Das Secret wird einmal zurückgegeben, beim Erstellen des Endpunkts.\n\n**Das eine, auf das Sie reagieren sollten, ist `case.extraction_completed`.** Ein Dokument zu lesen ist der langsame Schritt, und es ist der Schritt, nach dem sich ein erneutes Lesen des Falls lohnt. Eine Zustellung sagt, WAS sich geändert hat, und trägt die Fall-ID; sie trägt nicht den Fall, denn ein Empfänger, der einen Fall aus einem Strom von Ereignissen rekonstruiert, bekäme das falsch, und wir würden beide daran debuggen. Lesen Sie `GET /v1/anmeldungen/{id}` neu.\n\n**Die Zustellung ist at-least-once.** Ein Timeout, nachdem Ihr Server bestätigt hat, sieht genauso aus wie einer davor, Sie können also dasselbe Ereignis zweimal erhalten. Jede Zustellung trägt `Wisnots-Event-Id`; schlüsseln Sie darauf.\n\nPrüfen Sie die Signatur, bevor Sie einer Zustellung trauen:\n\n```\nWisnots-Signature: t=1788770000,v1=<hex hmac-sha256>\n```\n\nDer MAC geht über `${t}.${raw body}` mit dem Secret Ihres Endpunkts, und die rohen Bytes zählen: parsen Sie den Body nach dem Prüfen, nie davor. Weisen Sie eine Zustellung ab, deren `t` mehr als fünf Minuten von jetzt entfernt liegt, sonst bleibt eine abgefangene für immer gültig.\n\nNicht-2xx ist ein Fehlschlag und wird fünfmal über etwa zehn Minuten wiederholt. Zwanzig aufeinanderfolgende Ereignisse, die ihre Wiederholungen ausschöpfen, deaktivieren den Endpunkt, der dann auf `GET /v1/webhooks` `active: false` und einen `last_error` zeigt, sodass ein stummer Endpunkt von einem untätigen zu unterscheiden ist.\n\nEndpunkte gehören zu einer Umgebung, wie Schlüssel. Ein Sandbox-Endpunkt erhält nie die Ereignisse eines Live-Falls, sodass eine Testschleife nicht Ihre Produktionsabwicklung auslösen kann.\n\nDas sind alle fünf Anlagentypen, die der nationale Datensatz definiert. Es gibt keinen sechsten, ohne über den Standard hinauszugehen.",
    "contact": {
      "name": "Wisnots",
      "url": "https://wisnots.com"
    }
  },
  "servers": [
    {
      "url": "https://netz-api.wisnots.com",
      "description": "Die API. Ein Host bedient beide Umgebungen; auf welcher Sie sind, ergibt sich aus Ihrem Schlüssel, nicht aus der URL, es gibt also keine zweite Basis, auf die Sie beim Livegang wechseln müssten.\n\nEs ist nicht www.wisnots.com, das die Web-App ausliefert: eine Anfrage dorthin liefert die Seite mit einem 200, und ein Integrator kann nicht erkennen, warum nichts funktioniert."
    }
  ],
  "tags": [
    {
      "name": "Photovoltaik",
      "description": "Eine PV-Anlage in der Niederspannung, mit oder ohne Speicher.\n\nDer nationale Datensatz sieht für diesen Typ 104 Felder vor, 66 davon Pflicht: 35 werden in Antragphase 1 fällig, 41 in Antragphase 2 und 17 bei Inbetriebnahme. Zu den Blöcken, die alle Typen teilen, kommen Wechselrichter (Felder 3001 bis 3015), PV-Module (3101 bis 3103), Leistungsflussüberwachung (3201 bis 3206) und NA-Schutz (3301 bis 3305).\n\nZehn der Wechselrichterfelder markiert der Standard mit \"Keine Eingabe durch Nutzende empfohlen\": senden Sie die ZEREZ-ID und überlassen Sie den Rest dem Zertifikatsregister."
    },
    {
      "name": "Batteriespeicher",
      "description": "Eine Batterie, allein oder neben einer PV-Anlage.\n\nDer größte der fünf Typen des Standards: 107 Felder, 73 Pflicht, davon 33 in Antragphase 1, 41 in Antragphase 2 und 17 bei Inbetriebnahme. Der eigene Block sind die Felder 4001 bis 4023, den NA-Schutz (3301 bis 3305) teilt er mit der Photovoltaik.\n\nEs ist der eine Typ, der zugleich § 14a-Last und Erzeugungsanlage ist. Der Standard löst das innerhalb des Speicherblocks statt über zwei Familien, sodass ein Speicherfall die Fragen eines Typs stellt wie jeder andere, und die § 14a-Felder tragen dieselben Pfade wie eine Wallbox."
    },
    {
      "name": "Wärmepumpen",
      "description": "Eine Wärmepumpe, und mit ihr die Anmeldung nach § 14a als steuerbare Verbrauchseinrichtung, wo die installierte Leistung hinter dem Anschluss 4,2 kW übersteigt.\n\n90 Felder im Umfang, 59 Pflicht: 29 in Antragphase 1, 35 in Antragphase 2 und 15 bei Inbetriebnahme. Der Typblock sind die Felder 6001 bis 6018.\n\nEin POST hierhin legt `device.category` fest; Sie senden es nicht und können es nicht falsch machen. `/fields` neben dieser Route liefert nur die Liste für Wärmepumpen."
    },
    {
      "name": "Wallboxen und Ladepunkte",
      "description": "Ein nicht öffentlicher Ladepunkt, und mit ihm die Anmeldung nach § 14a ab 4,2 kW. Oberhalb der eigenen Schwelle des Netzbetreibers folgt ein Zustimmungsverfahren nach § 19 NAV, das wir auf dem Fall anzeigen, statt von Ihnen zu verlangen, es zu kennen.\n\n94 Felder im Umfang, 61 Pflicht: 29 in Antragphase 1, 36 in Antragphase 2 und 15 bei Inbetriebnahme. Der Typblock sind die Felder 5001 bis 5020.\n\nEin POST hierhin legt `device.category` fest, und `/fields` daneben ist auf Ladepunkte eingegrenzt."
    },
    {
      "name": "Klimaanlagen",
      "description": "Eine Anlage zur Raumkühlung, und die Anmeldung nach § 14a, die ab 4,2 kW dazugehört.\n\n88 Felder im Umfang, 58 Pflicht: 29 in Antragphase 1, 33 in Antragphase 2 und 15 bei Inbetriebnahme. Der Typblock sind die Felder 7001 bis 7017.\n\nDas vierte und letzte der § 14a-Geräte, und eine reine Last: keine Erzeugung, kein NA-Schutz und keine Vergütungsfelder, es stellt also weniger Fragen als jeder andere Typ. Seine § 14a-Felder tragen dieselben Pfade wie eine Wallbox."
    },
    {
      "name": "§ 14a, beliebiges Gerät",
      "description": "Die Regime-Route, erhalten für Aufrufer, die einen Fall eröffnen, bevor jemand entschieden hat, um welches Gerät es geht, und für die zwei § 14a-Geräte, die noch keine typisierte Route haben.\n\nEs ist der eine Erstellpfad, der `device.category` nicht festlegt; Sie deklarieren es selbst, und `/fields` braucht `?category=` zur Eingrenzung. Bevorzugen Sie eine typisierte Route, wo es eine gibt: die URL trägt dann den Sachverhalt, und eine vertippte Kategorie kann keine Wallbox als Wärmepumpe einreichen."
    },
    {
      "name": "Der Fall",
      "description": "Was ein Fall tut, nachdem er existiert, identisch, welche Route ihn auch erzeugt hat.\n\nLesen, korrigieren, mehr zu lesen geben, einreichen, das Protokoll abspielen. Einen zweiten Anlagentyp später zu integrieren ist daher ein neuer Erstellaufruf und sonst nichts auf dieser Seite.\n\nEs gibt kein PUT. `PATCH` ist bewusst partiell und additiv: ein Ersetzen der ganzen Ressource ließe einen Aufrufer still ein Feld leeren, das eine andere Partei deklariert hat, und auf einem Fall, in den zwei Menschen und eine Erkennung schreiben, ist das eine Einreichung mit einem Wert, den niemand entfernen wollte."
    },
    {
      "name": "Schlüssel und Umgebungen",
      "description": "Einen Schlüssel bekommen, und auf welche Umgebung er Sie setzt.\n\nDie Umgebung ergibt sich aus dem Schlüssel, nie aus der URL oder einem Anfragefeld; es gibt also keine Basis-URL, die beim Livegang zu wechseln wäre, und kein Flag, das versehentlich gesetzt bleibt. Fall-IDs sagen es Ihnen zurück: `anm_` live, `anm_test_` in der Sandbox.\n\nSchlüssel werden in der Konsole unter https://www.wisnots.com/netzanmeldung/portal, unter Entwickler, auf beiden Umgebungen erzeugt: jedes angemeldete Konto kann einen Sandbox-Schlüssel und einen Live-Schlüssel erstellen. Ein kostenloses Konto genügt; die Sandbox ist kostenlos, liest Ihre eigenen Dokumente und liefert Webhooks, sodass die ganze Integration gebaut ist, bevor etwas Echtes eingereicht wird."
    }
  ],
  "x-tagGroups": [
    {
      "name": "Anmeldearten",
      "tags": [
        "Photovoltaik",
        "Batteriespeicher",
        "Wärmepumpen",
        "Wallboxen und Ladepunkte",
        "Klimaanlagen",
        "§ 14a, beliebiges Gerät"
      ]
    },
    {
      "name": "Vorgang",
      "tags": [
        "Der Fall"
      ]
    },
    {
      "name": "Zugang",
      "tags": [
        "Schlüssel und Umgebungen"
      ]
    },
    {
      "name": "Netzbetreiber",
      "tags": [
        "Netzbetreiber"
      ]
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/netzbetreiber": {
      "get": {
        "operationId": "listOperators",
        "summary": "Welche Netzbetreiber, und welcher hier",
        "description": "Jeder Netzbetreiber, den wir führen, und was jeder entgegennehmen kann, und mit `plz`, welche davon eine Postleitzahl versorgen.\n\n`operator` beim Erstellen bleibt ausdrücklich: übergeben Sie den Schlüssel, den dies zurückgibt. Ein falsch geratener Netzbetreiber ändert, welche Fragen ein Fall beantworten muss, deshalb bindet die API nie von sich aus einen aus einer Adresse. Ein Postleitzahlgebiet überschreitet Netzgrenzen oft genug, dass die Antwort zwei Netzbetreiber sein kann; dann ist `resolved` false, und die Wahl liegt bei Ihrem Nutzer, mit beiden Namen zur Auswahl.\n\nDieselbe Liste auf beiden Umgebungen, und dieselbe Liste für jeden Schlüssel: welche Netzbetreiber es gibt, ist weder ein Geheimnis noch ein Modus.",
        "tags": [
          "Netzbetreiber"
        ],
        "parameters": [
          {
            "name": "plz",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{5}$"
            },
            "description": "Eine deutsche Postleitzahl, fünf Ziffern, führende Null eingeschlossen. Damit ist die Antwort die Netzbetreiber, die diese Postleitzahl versorgen, plus `resolved`; ohne sie jeder Netzbetreiber."
          }
        ],
        "responses": {
          "200": {
            "description": "Netzbetreiber.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string",
                            "description": "Was beim Erstellen als `operator` zu senden ist."
                          },
                          "name": {
                            "type": "string",
                            "description": "Der Name des Netzbetreibers, zur Anzeige für Ihren Nutzer."
                          },
                          "sandbox": {
                            "type": "boolean",
                            "description": "True für den fiktiven Sandbox-Netzbetreiber, dessen Formular sich nie ändert. Wird nie aus einer Postleitzahl aufgelöst, und auf einem Live-Schlüssel abgelehnt."
                          },
                          "filings": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "kind": {
                                  "type": "string",
                                  "description": "Eine Art von Einreichung, z. B. `section_14a`."
                                },
                                "submission": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "enum": [
                                    "email",
                                    "portal",
                                    null
                                  ],
                                  "description": "Wie dieser Netzbetreiber die Einreichung entgegennimmt. Null, wenn wir es nicht erfasst haben."
                                },
                                "readiness": {
                                  "type": "string",
                                  "enum": [
                                    "none",
                                    "address",
                                    "form",
                                    "mapped",
                                    "verified"
                                  ],
                                  "description": "Wie weit wir eine Einreichung dieser Art hier bringen können, kumulativ: `address` heißt, wir wissen, wohin sie geht, `form`, dass wir das Formular des Netzbetreibers haben, `mapped`, dass wir es ausfüllen können, `verified`, dass dieser Netzbetreiber eine angenommen hat. `none` heißt, ein Fall kann vorbereitet werden und endet bei uns."
                                }
                              },
                              "required": [
                                "kind",
                                "submission",
                                "readiness"
                              ]
                            }
                          }
                        },
                        "required": [
                          "key",
                          "name",
                          "sandbox",
                          "filings"
                        ]
                      }
                    },
                    "plz": {
                      "type": "string",
                      "description": "Zurückgegeben, wenn angegeben."
                    },
                    "resolved": {
                      "type": "boolean",
                      "description": "Nur mit `plz`. True, wenn genau ein Netzbetreiber sie versorgt, sodass `data[0].key` als `operator` gesendet werden kann, ohne jemanden zu fragen. False bei keinem oder bei mehreren."
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "422": {
            "description": "`plz` ist keine fünfstellige Postleitzahl.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "operationId": "createWebhook",
        "summary": "Einen Endpunkt registrieren",
        "description": "Liefert jedes Ereignis der Umgebung dieses Schlüssels signiert an Ihre URL.\n\n**Das Secret kommt einmal zurück, hier.** Wir signieren damit und Sie prüfen damit; nichts braucht es ein zweites Mal lesbar, und ein Secret, das eine API erneut zeigt, ist eines, das über jeden Screenshot dieses Aufrufs entweicht.\n\nDie URL muss https sein und darf keine Loopback- oder private Adresse sein: dieser Endpunkt lässt unseren Server abrufen, was Sie ihm geben.",
        "tags": [
          "Schlüssel und Umgebungen"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "types": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "case.created",
                        "case.material_read",
                        "case.extraction_completed",
                        "case.updated",
                        "case.status_changed",
                        "case.filed",
                        "case.information_requested",
                        "case.information_response_received",
                        "case.information_response_ready",
                        "case.information_response_submitted",
                        "case.operator_login_missing"
                      ]
                    },
                    "description": "Eine Teilmenge abonnieren. Weglassen für jeden Typ. Ein unbekannter Typ wird namentlich abgelehnt, statt still nie auszulösen."
                  },
                  "description": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Endpunkt, und sein Secret.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "secret": {
                      "type": "string",
                      "example": "whsec_...",
                      "description": "Wird einmal gezeigt. Speichern Sie es jetzt."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Die URL oder die Typliste wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listWebhooks",
        "summary": "Ihre Endpunkte",
        "description": "Jeder Endpunkt der Umgebung dieses Schlüssels, mit genug Zustellstatus, um einen stummen Endpunkt von einem untätigen zu unterscheiden. Nie das Secret.",
        "tags": [
          "Schlüssel und Umgebungen"
        ],
        "responses": {
          "200": {
            "description": "Endpunkte.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "mode": {
                            "type": "string",
                            "enum": [
                              "live",
                              "test"
                            ]
                          },
                          "active": {
                            "type": "boolean",
                            "description": "False, sobald durch wiederholtes Scheitern deaktiviert."
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "types": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "case.created",
                                "case.material_read",
                                "case.extraction_completed",
                                "case.updated",
                                "case.status_changed",
                                "case.filed",
                                "case.information_requested",
                                "case.information_response_received",
                                "case.information_response_ready",
                                "case.information_response_submitted",
                                "case.operator_login_missing"
                              ]
                            },
                            "description": "Fehlt: jeder Typ."
                          },
                          "description": {
                            "type": "string"
                          },
                          "last_attempt_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "last_status": {
                            "type": "integer",
                            "description": "HTTP-Status, mit dem Ihr Endpunkt geantwortet hat."
                          },
                          "last_error": {
                            "type": "string"
                          },
                          "disabled_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "consecutive_failures": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{webhookId}": {
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Einen Endpunkt entfernen",
        "description": "Zustellungen enden sofort. Eine ID des anderen Modus ist ein 404, kein 403: ob sie existiert, geht diesen Schlüssel ebenfalls nichts an.",
        "tags": [
          "Schlüssel und Umgebungen"
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Entfernt."
          },
          "404": {
            "description": "Kein solcher Endpunkt auf dieser Umgebung.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/pv": {
      "post": {
        "operationId": "createPvAnmeldung",
        "summary": "Eine PV-Anlage anmelden",
        "description": "**Drei Bodys zum Ausprobieren.** Unter *Request Example* rechts wechselt die Auswahl neben *Test Request* zwischen einem **Fixture**, das die Sandbox kostenlos liest, der **rohen** Form (die Kundenmail und zwei Dateien, nichts Strukturiertes) und **deklarierten** Feldern aus Ihrem CRM. Was dort gewählt ist, öffnet *Test Request*; im Client liegen dieselben Bodys in der Seitenleiste unter dieser Anfrage. Eigene Dateien gehen an *Dateien hochladen*, die Operation direkt nach dieser, die sie als Formular mit Dateiauswahl entgegennimmt.\n\n**Der Pfad legt `device.category` auf `pv` fest.** Lassen Sie das Feld weg.\n\nDas Erzeugungsgegenstück zu `POST /v1/anmeldungen/14a`, und ein eigener Pfad, weil die Fragen sich unterscheiden: PV will einen Wechselrichterblock und die Modulleistung, wo § 14a eine Modulwahl und einen Nachweis der Zustimmung will.\n\n`device.category` wird nicht gefragt. Die Wahl dieses Pfads hat es bereits beantwortet, und der Fall meldet es als `derived`. Ein `declared.device.category`, das nicht `pv` ist, ist ein 422, der beide Hälften benennt, statt eines Falls, der nach den Fragen der einen Familie und den Antworten einer anderen beurteilt wird.\n\nAlles nach dem Erstellen ist identisch mit jedem anderen Fall: `GET`, `PATCH`, `material`, `file` und `events` liegen alle auf `/v1/anmeldungen/{id}` und wissen nichts von Familien.\n\n### Wechselrichter\n\nFünf Felder werden EINMAL PRO WECHSELRICHTER gefragt; der Standard markiert sie als `wiederholt`. Senden Sie sie als Array:\n\n```json\n{\n  \"declared\": {\n    \"device\": { \"inverter_count\": 2, \"apparent_power_kva\": 20 },\n    \"inverters\": [\n      { \"manufacturer\": \"Fronius\", \"apparent_power_kva\": 10 },\n      { \"manufacturer\": \"SMA\", \"apparent_power_kva\": 10 }\n    ]\n  }\n}\n```\n\nSie kommen in `open` und `fields` einzeln pro Instanz zurück, `inverters.1.apparent_power_kva`, sodass eine Lücke benennt, WELCHEM Wechselrichter was fehlt. Das Wörterbuch veröffentlicht sie als Vorlagen mit `[]` an der Stelle des Index; diese Form ist ein Name, kein Ort zum Schreiben.\n\nWas eine PV-Einreichung blockiert, ist die Nennleistung, im Standard `Antragphase 1`: was ein Netzbetreiber braucht, um ein Netzanschlussbegehren überhaupt beantworten zu können. Hersteller, Typ und ZEREZ-ID sind Phase 2: echte Anforderungen, spätere. Die Vergütungsfelder (MaStR, Steuer, Bank) kommen noch später und blockieren nie: der Datensatz beschränkt sich auf den Netzanschlussprozess, und bezahlt zu werden ist ein anderer Prozess.",
        "tags": [
          "Photovoltaik"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "examples": {
                "fixture": {
                  "summary": "Fixture: eine gehostete PV-Rechnung, kostenlos, kein Modellaufruf",
                  "value": {
                    "external_ref": "ERP-5101",
                    "intent": "draft",
                    "material": [
                      {
                        "fixture": "pv-invoice-de"
                      }
                    ]
                  }
                },
                "raw": {
                  "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
                  "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
                  "value": {
                    "external_ref": "ERP-5103",
                    "intent": "draft",
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo,\n\nanbei die Rechnung und ein Foto vom Typenschild des Wechselrichters zu unserer neuen PV-Anlage (9,84 kWp, ein Wechselrichter Fronius Symo GEN24 10.0 Plus). Zaehlernummer ist 1ESY1189004412. Bitte melden Sie die Anlage beim Netzbetreiber an, wir moechten die Einspeiseverguetung nach EEG.\n\nViele Gruesse\nInes Kaufmann\nHeinrichstrasse 118, 64283 Darmstadt"
                      },
                      {
                        "kind": "file",
                        "filename": "rechnung-sonnenstrom.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQK"
                      },
                      {
                        "kind": "file",
                        "filename": "typenschild-wechselrichter.jpg",
                        "content_type": "image/jpeg",
                        "data": "/9j/4AAQSkZJRgAB"
                      }
                    ]
                  }
                },
                "declared": {
                  "summary": "Deklariert: Felder aus Ihrem CRM, ein Wechselrichter, 9,84 kWp",
                  "value": {
                    "external_ref": "ERP-5102",
                    "intent": "draft",
                    "declared": {
                      "site": {
                        "street": "Heinrichstrasse",
                        "house_number": "118",
                        "postal_code": "64283",
                        "city": "Darmstadt"
                      },
                      "end_customer": {
                        "first_name": "Ines",
                        "last_name": "Kaufmann",
                        "email": "i.kaufmann@example.de",
                        "phone": "+49 6151 5550188",
                        "is_owner": true
                      },
                      "installer": {
                        "company": "Sonnenstrom Hessen GmbH",
                        "street": "Rheinstrasse",
                        "house_number": "40",
                        "postal_code": "64293",
                        "city": "Darmstadt",
                        "email": "info@sonnenstrom-hessen.example"
                      },
                      "device": {
                        "inverter_count": 1,
                        "apparent_power_kva": 10,
                        "active_power_kw": 10,
                        "module_power_kwp": 9.84,
                        "installed_at": "2026-06-12",
                        "status": "planned"
                      },
                      "inverters": [
                        {
                          "manufacturer": "Fronius",
                          "model": "Symo GEN24 10.0 Plus",
                          "apparent_power_kva": 10,
                          "active_power_kw": 10
                        }
                      ],
                      "protection": {
                        "manufacturer": "Ziehl",
                        "model": "UFR1001E"
                      },
                      "connection": {
                        "meter_number": "1ESY1189004412"
                      },
                      "feed_in": {
                        "sale_form": "feed_in_tariff"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Fehlender oder ungültiger API-Schlüssel.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Der Body ist fehlerhaft, oder sein Gerät gehört zu einer anderen Familie.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Ratenbegrenzung.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/pv/upload": {
      "post": {
        "operationId": "uploadPv",
        "summary": "Dateien hochladen für eine PV-Anlage",
        "description": "Eröffnet den Fall aus Dateien, so wie ein Backoffice sie hat: die Dokumente, die Kundenmail, und welche Felder Sie schon vorliegen haben. `curl -F file=@rechnung.pdf -F file=@typenschild.jpg -F text=<kundenmail.txt` auf diese Route antwortet mit dem neuen Fall und allen dreien unter `material[]`, bereits im Lesen.\n\nEin `file`-Part pro Dokument (ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei), `text` für eine eingefügte Mail, und die Felder des JSON-Bodys als weitere Parts, `declared` als JSON-Text. Was eine Datei ist, ergibt sich aus ihren Bytes, der Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert. Leere Parts werden ignoriert, sodass das Formular, das der Client dieser Seite baut, jede Zeile angehakt, durchgeht.\n\nDer Fall, der zurückkommt, ist derselbe, den `POST /v1/anmeldungen/pv` liefert, und alles danach ist identisch: lesen, korrigieren, mit `POST /{id}/upload` ergänzen, einreichen. Ein hierher gesendeter JSON-Body ist ein 415, der die Erstellroute benennt; die beiden Türen überlappen nicht, sodass keine eine zweite Form zu lernen hat.",
        "tags": [
          "Photovoltaik"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "installer_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "file"
                    ],
                    "default": "draft",
                    "description": "Wie im JSON-Body."
                  },
                  "operator": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Wie im JSON-Body."
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das `declared`-Objekt des JSON-Bodys, als JSON-Text."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: zwei Dateien und die Kundenmail",
                  "description": "Wählen Sie die Dateien; die übrigen Parts sind Text. Die Antwort ist der Fall mit allem unter `material[]`, bereits im Lesen.",
                  "value": {
                    "text": "Hallo, anbei Rechnung und Typenschild. Zaehlernummer 1ESY1189004412.",
                    "external_ref": "ERP-9001",
                    "intent": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf die Erstellroute gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein Part wurde abgelehnt, mit seinem Namen.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/pv/fields": {
      "get": {
        "operationId": "getPvFieldDictionary",
        "summary": "Das PV-Feldwörterbuch",
        "description": "Jedes Feld, das ein PV-Fall versteht, durch den Pfad eingegrenzt, sodass kein `category`-Parameter nötig ist: PV lässt genau eine zu, und ein Parameter wäre nur eine Möglichkeit, ihr zu widersprechen.\n\nWiederholte Felder erscheinen einmal, als Vorlagen: `inverters[].manufacturer`. Bauen Sie aus jeder eine Zeile Ihres Formulars und wiederholen Sie die Zeile.",
        "tags": [
          "Photovoltaik"
        ],
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Das Wörterbuch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "surface": {
                      "type": "string",
                      "enum": [
                        "universal",
                        "operator"
                      ]
                    },
                    "operator": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "version",
                    "surface",
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/speicher": {
      "post": {
        "operationId": "createSpeicher",
        "summary": "Einen Batteriespeicher anmelden",
        "description": "**Drei Bodys zum Ausprobieren.** Unter *Request Example* rechts wechselt die Auswahl neben *Test Request* zwischen einem **Fixture**, das die Sandbox kostenlos liest, der **rohen** Form (die Kundenmail und zwei Dateien, nichts Strukturiertes) und **deklarierten** Feldern aus Ihrem CRM. Was dort gewählt ist, öffnet *Test Request*; im Client liegen dieselben Bodys in der Seitenleiste unter dieser Anfrage. Eigene Dateien gehen an *Dateien hochladen*, die Operation direkt nach dieser, die sie als Formular mit Dateiauswahl entgegennimmt.\n\nEröffnet einen Fall für einen Batteriespeicher, allein oder neben einer PV-Anlage.\n\n**Der Pfad legt `device.category` auf `battery` fest.** Lassen Sie das Feld weg; ein anderer Wert ist ein 400 statt eines stillen Überschreibens.\n\nSpeicher ist eine eigene Familie, kein § 14a-Fall mit einem anderen Gerät, und der größte Typ des Standards: 107 Felder, 73 davon Pflicht. Er ist eine steuerbare Last, während er lädt, und eine Erzeugungsanlage, während er entlädt, und der Standard löst das innerhalb seines eigenen Blocks statt über zwei: die Felder 4011, 4015, 4016, 4017 und 4023 sind die § 14a-Fragen, und sie tragen dieselben Pfade wie eine Wallbox, sodass ein einmal gebauter § 14a-Bildschirm nicht noch einmal gebaut werden muss. Der NA-Schutz (3301 bis 3305) wird mit PV geteilt.\n\nVier Felder blockieren, und sie sind die Antragphase 1 des Standards: `device.count`, `device.apparent_power_kva`, `device.coupling` und `device.operating_mode`. Alles andere darf folgen.\n\nWas entsteht, ist eine gewöhnliche Anmeldung, gelesen und eingereicht auf `/v1/anmeldungen/{id}` wie jede andere.",
        "tags": [
          "Batteriespeicher"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "examples": {
                "fixture": {
                  "summary": "Fixture: eine gehostete Speicherrechnung, kostenlos, kein Modellaufruf",
                  "value": {
                    "external_ref": "ERP-5201",
                    "intent": "draft",
                    "material": [
                      {
                        "fixture": "battery-invoice-de"
                      }
                    ]
                  }
                },
                "raw": {
                  "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
                  "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
                  "value": {
                    "external_ref": "ERP-5203",
                    "intent": "draft",
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo,\n\nim Anhang Rechnung und Typenschild zu unserem Stromspeicher (BYD Battery-Box Premium HVS 10.2, 10,24 kWh, eigener Wechselrichter). Zaehlernummer 1ESY1170551234. Der Speicher soll nach 14a EnWG mit Modul 1 angemeldet werden.\n\nViele Gruesse\nJonas Petersen\nKanalufer 9, 24106 Kiel"
                      },
                      {
                        "kind": "file",
                        "filename": "rechnung-nordkap.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQK"
                      },
                      {
                        "kind": "file",
                        "filename": "typenschild-speicher.jpg",
                        "content_type": "image/jpeg",
                        "data": "/9j/4AAQSkZJRgAB"
                      }
                    ]
                  }
                },
                "declared": {
                  "summary": "Deklariert: Felder aus Ihrem CRM, 10,24 kWh, AC-gekoppelt",
                  "value": {
                    "external_ref": "ERP-5202",
                    "intent": "draft",
                    "declared": {
                      "site": {
                        "street": "Kanalufer",
                        "house_number": "9",
                        "postal_code": "24106",
                        "city": "Kiel"
                      },
                      "end_customer": {
                        "first_name": "Jonas",
                        "last_name": "Petersen",
                        "email": "j.petersen@example.de",
                        "phone": "+49 431 5550172",
                        "is_owner": true
                      },
                      "installer": {
                        "company": "Elektro Nordkap GmbH",
                        "email": "kontakt@elektro-nordkap.example"
                      },
                      "device": {
                        "count": 1,
                        "manufacturer": "BYD",
                        "model": "Battery-Box Premium HVS 10.2",
                        "apparent_power_kva": 5,
                        "active_power_kw": 5,
                        "storage_capacity_kwh": 10.24,
                        "coupling": "Eigener Wechselrichter (AC-Kopplung)",
                        "installed_at": "2026-07-02",
                        "status": "planned"
                      },
                      "section_14a": {
                        "is_controllable": true,
                        "control_responsibility": "metering_operator",
                        "modules": "1",
                        "consent": {
                          "given_by": "end_customer",
                          "given_at": "2026-07-01T09:30:00Z",
                          "channel": "email"
                        }
                      },
                      "connection": {
                        "meter_number": "1ESY1170551234"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein deklarierter Wert wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "validation_failed": {
                    "$ref": "#/components/examples/ProblemValidation"
                  }
                }
              }
            }
          },
          "402": {
            "description": "Keine Credits mehr. Der Fall wird nicht erstellt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Ratenbegrenzung. Retry-After ist gesetzt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/speicher/upload": {
      "post": {
        "operationId": "uploadSpeicher",
        "summary": "Dateien hochladen für einen Batteriespeicher",
        "description": "Eröffnet den Fall aus Dateien, so wie ein Backoffice sie hat: die Dokumente, die Kundenmail, und welche Felder Sie schon vorliegen haben. `curl -F file=@rechnung.pdf -F file=@typenschild.jpg -F text=<kundenmail.txt` auf diese Route antwortet mit dem neuen Fall und allen dreien unter `material[]`, bereits im Lesen.\n\nEin `file`-Part pro Dokument (ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei), `text` für eine eingefügte Mail, und die Felder des JSON-Bodys als weitere Parts, `declared` als JSON-Text. Was eine Datei ist, ergibt sich aus ihren Bytes, der Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert. Leere Parts werden ignoriert, sodass das Formular, das der Client dieser Seite baut, jede Zeile angehakt, durchgeht.\n\nDer Fall, der zurückkommt, ist derselbe, den `POST /v1/anmeldungen/speicher` liefert, und alles danach ist identisch: lesen, korrigieren, mit `POST /{id}/upload` ergänzen, einreichen. Ein hierher gesendeter JSON-Body ist ein 415, der die Erstellroute benennt; die beiden Türen überlappen nicht, sodass keine eine zweite Form zu lernen hat.",
        "tags": [
          "Batteriespeicher"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "installer_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "file"
                    ],
                    "default": "draft",
                    "description": "Wie im JSON-Body."
                  },
                  "operator": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Wie im JSON-Body."
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das `declared`-Objekt des JSON-Bodys, als JSON-Text."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: zwei Dateien und die Kundenmail",
                  "description": "Wählen Sie die Dateien; die übrigen Parts sind Text. Die Antwort ist der Fall mit allem unter `material[]`, bereits im Lesen.",
                  "value": {
                    "text": "Hallo, anbei Rechnung und Typenschild. Zaehlernummer 1ESY1189004412.",
                    "external_ref": "ERP-9001",
                    "intent": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf die Erstellroute gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein Part wurde abgelehnt, mit seinem Namen.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/speicher/fields": {
      "get": {
        "operationId": "getSpeicherFieldDictionary",
        "summary": "Felder für einen Batteriespeicher",
        "description": "Jedes Feld, das für einen Batteriespeicher gilt, mit seiner Feldnummer, der Bezeichnung und dem Hilfetext des Standards, seinem Typ und seinen Optionen, wann es Pflicht ist, und von wem die Antwort erwartet wird.\n\nBereits durch den Pfad eingegrenzt, es gibt also keinen `category`-Parameter, den man falsch setzen könnte.",
        "tags": [
          "Batteriespeicher"
        ],
        "parameters": [
          {
            "name": "operator",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
            },
            "description": "Die eigenen Anforderungen dieses Netzbetreibers einschließen, markiert als `operator_specific`. Ohne Wirkung unter einem universellen Schlüssel, der bewusst dieselbe Feldliste sieht, welchen Netzbetreiber er auch nennt; siehe `requirements` auf einem Fall."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Das Wörterbuch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "surface": {
                      "type": "string",
                      "enum": [
                        "universal",
                        "operator"
                      ],
                      "description": "Welche Oberfläche Ihnen geantwortet hat. Zurückgegeben, damit Sie sich nicht merken müssen, welchen Schlüssel Sie benutzt haben."
                    },
                    "operator": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "version",
                    "surface",
                    "data"
                  ]
                },
                "examples": {
                  "slice": {
                    "$ref": "#/components/examples/FieldDictionarySlice"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/waermepumpe": {
      "post": {
        "operationId": "createWaermepumpe",
        "summary": "Eine Wärmepumpe anmelden",
        "description": "**Drei Bodys zum Ausprobieren.** Unter *Request Example* rechts wechselt die Auswahl neben *Test Request* zwischen einem **Fixture**, das die Sandbox kostenlos liest, der **rohen** Form (die Kundenmail und zwei Dateien, nichts Strukturiertes) und **deklarierten** Feldern aus Ihrem CRM. Was dort gewählt ist, öffnet *Test Request*; im Client liegen dieselben Bodys in der Seitenleiste unter dieser Anfrage. Eigene Dateien gehen an *Dateien hochladen*, die Operation direkt nach dieser, die sie als Formular mit Dateiauswahl entgegennimmt.\n\nEröffnet einen Fall für eine Wärmepumpe aus deklarierten Feldern, rohem Material, oder beidem.\n\n**Der Pfad legt `device.category` auf `heat_pump` fest.** Lassen Sie das Feld weg; ein anderer Wert ist ein 400 statt eines stillen Überschreibens, denn eine still korrigierte Kategorie ist eine Einreichung an den falschen Schreibtisch.\n\nDer Typblock sind die Felder 6001 bis 6018. Die Anmeldung als steuerbare Verbrauchseinrichtung nach § 14a folgt aus der deklarierten Leistung, es gibt also keinen zweiten Aufruf und kein Flag zu setzen.\n\nDie Erkennung läuft für Inline-Material sofort, und die Antwort spiegelt sie bei kleinen Eingaben bereits wider; ein Fall, der noch gelesen wird, kommt als `extracting` zurück und sollte abgefragt oder per Webhook abgewartet werden.\n\nWas entsteht, ist eine gewöhnliche Anmeldung. Alles danach geschieht auf `/v1/anmeldungen/{id}` und ist für jeden Fall gleich, gleich was ihn eröffnet hat.",
        "tags": [
          "Wärmepumpen"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "examples": {
                "fixture": {
                  "summary": "Fixture: eine gehostete Wärmepumpenrechnung, kostenlos, kein Modellaufruf",
                  "value": {
                    "external_ref": "ERP-5301",
                    "intent": "draft",
                    "material": [
                      {
                        "fixture": "heat-pump-invoice-de"
                      }
                    ]
                  }
                },
                "raw": {
                  "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
                  "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
                  "value": {
                    "external_ref": "ERP-5303",
                    "intent": "draft",
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo,\n\nanbei Rechnung und Typenschild unserer Waermepumpe (Vaillant aroTHERM plus VWL 75/6 A, Luft-Wasser). Sie laeuft seit Mai. Zaehlernummer 1ESY1170099887. Bitte Modul 1 nach 14a EnWG anmelden, die Steuerung uebernimmt der Netzbetreiber.\n\nViele Gruesse\nPetra Ahlers\nWiesenweg 3, 22607 Hamburg"
                      },
                      {
                        "kind": "file",
                        "filename": "rechnung-nordlicht.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQK"
                      },
                      {
                        "kind": "file",
                        "filename": "typenschild-waermepumpe.jpg",
                        "content_type": "image/jpeg",
                        "data": "/9j/4AAQSkZJRgAB"
                      }
                    ]
                  }
                },
                "declared": {
                  "summary": "Deklariert: Felder aus Ihrem CRM, Luft-Wasser, 12 A Anlaufstrom",
                  "value": {
                    "external_ref": "ERP-5302",
                    "intent": "draft",
                    "declared": {
                      "site": {
                        "street": "Wiesenweg",
                        "house_number": "3",
                        "postal_code": "22607",
                        "city": "Hamburg"
                      },
                      "end_customer": {
                        "first_name": "Petra",
                        "last_name": "Ahlers",
                        "email": "p.ahlers@example.de",
                        "phone": "+49 40 5550199",
                        "is_owner": true
                      },
                      "installer": {
                        "company": "Sanitaer & Waerme Nordlicht GmbH",
                        "email": "buero@nordlicht-waerme.example"
                      },
                      "device": {
                        "count": 1,
                        "manufacturer": "Vaillant",
                        "model": "aroTHERM plus VWL 75/6 A",
                        "heat_pump_type": "air_water",
                        "starting_current_a": 12,
                        "installed_at": "2026-05-20",
                        "status": "installed"
                      },
                      "section_14a": {
                        "control_responsibility": "grid_operator",
                        "modules": "1",
                        "consent": {
                          "given_by": "end_customer",
                          "given_at": "2026-05-18T09:30:00Z",
                          "channel": "phone"
                        }
                      },
                      "connection": {
                        "meter_number": "1ESY1170099887"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein deklarierter Wert wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "validation_failed": {
                    "$ref": "#/components/examples/ProblemValidation"
                  }
                }
              }
            }
          },
          "402": {
            "description": "Keine Credits mehr. Der Fall wird nicht erstellt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Ratenbegrenzung. Retry-After ist gesetzt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/waermepumpe/upload": {
      "post": {
        "operationId": "uploadWaermepumpe",
        "summary": "Dateien hochladen für eine Wärmepumpe",
        "description": "Eröffnet den Fall aus Dateien, so wie ein Backoffice sie hat: die Dokumente, die Kundenmail, und welche Felder Sie schon vorliegen haben. `curl -F file=@rechnung.pdf -F file=@typenschild.jpg -F text=<kundenmail.txt` auf diese Route antwortet mit dem neuen Fall und allen dreien unter `material[]`, bereits im Lesen.\n\nEin `file`-Part pro Dokument (ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei), `text` für eine eingefügte Mail, und die Felder des JSON-Bodys als weitere Parts, `declared` als JSON-Text. Was eine Datei ist, ergibt sich aus ihren Bytes, der Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert. Leere Parts werden ignoriert, sodass das Formular, das der Client dieser Seite baut, jede Zeile angehakt, durchgeht.\n\nDer Fall, der zurückkommt, ist derselbe, den `POST /v1/anmeldungen/waermepumpe` liefert, und alles danach ist identisch: lesen, korrigieren, mit `POST /{id}/upload` ergänzen, einreichen. Ein hierher gesendeter JSON-Body ist ein 415, der die Erstellroute benennt; die beiden Türen überlappen nicht, sodass keine eine zweite Form zu lernen hat.",
        "tags": [
          "Wärmepumpen"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "installer_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "file"
                    ],
                    "default": "draft",
                    "description": "Wie im JSON-Body."
                  },
                  "operator": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Wie im JSON-Body."
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das `declared`-Objekt des JSON-Bodys, als JSON-Text."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: zwei Dateien und die Kundenmail",
                  "description": "Wählen Sie die Dateien; die übrigen Parts sind Text. Die Antwort ist der Fall mit allem unter `material[]`, bereits im Lesen.",
                  "value": {
                    "text": "Hallo, anbei Rechnung und Typenschild. Zaehlernummer 1ESY1189004412.",
                    "external_ref": "ERP-9001",
                    "intent": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf die Erstellroute gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein Part wurde abgelehnt, mit seinem Namen.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/waermepumpe/fields": {
      "get": {
        "operationId": "getWaermepumpeFieldDictionary",
        "summary": "Felder für eine Wärmepumpe",
        "description": "Jedes Feld, das für eine Wärmepumpe gilt, mit seiner Feldnummer, der Bezeichnung und dem Hilfetext des Standards, seinem Typ und seinen Optionen, wann es Pflicht ist, und von wem die Antwort erwartet wird.\n\nBereits durch den Pfad eingegrenzt, es gibt also keinen `category`-Parameter, den man falsch setzen könnte. Bauen Sie Ihr Erfassungsformular hieraus, statt unser Schema fest zu verdrahten, und es funktioniert weiter, während das Wörterbuch wächst.",
        "tags": [
          "Wärmepumpen"
        ],
        "parameters": [
          {
            "name": "operator",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
            },
            "description": "Die eigenen Anforderungen dieses Netzbetreibers einschließen, markiert als `operator_specific`. Ohne Wirkung unter einem universellen Schlüssel, der bewusst dieselbe Feldliste sieht, welchen Netzbetreiber er auch nennt; siehe `requirements` auf einem Fall."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Das Wörterbuch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "surface": {
                      "type": "string",
                      "enum": [
                        "universal",
                        "operator"
                      ],
                      "description": "Welche Oberfläche Ihnen geantwortet hat. Zurückgegeben, damit Sie sich nicht merken müssen, welchen Schlüssel Sie benutzt haben."
                    },
                    "operator": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "version",
                    "surface",
                    "data"
                  ]
                },
                "examples": {
                  "slice": {
                    "$ref": "#/components/examples/FieldDictionarySlice"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/ladeeinrichtung": {
      "post": {
        "operationId": "createLadeeinrichtung",
        "summary": "Einen Ladepunkt anmelden",
        "description": "**Drei Bodys zum Ausprobieren.** Unter *Request Example* rechts wechselt die Auswahl neben *Test Request* zwischen einem **Fixture**, das die Sandbox kostenlos liest, der **rohen** Form (die Kundenmail und zwei Dateien, nichts Strukturiertes) und **deklarierten** Feldern aus Ihrem CRM. Was dort gewählt ist, öffnet *Test Request*; im Client liegen dieselben Bodys in der Seitenleiste unter dieser Anfrage. Eigene Dateien gehen an *Dateien hochladen*, die Operation direkt nach dieser, die sie als Formular mit Dateiauswahl entgegennimmt.\n\nEröffnet einen Fall für einen Ladepunkt aus deklarierten Feldern, rohem Material, oder beidem.\n\n**Der Pfad legt `device.category` auf `charge_point` fest.** Lassen Sie das Feld weg; ein anderer Wert ist ein 400 statt eines stillen Überschreibens, denn eine still korrigierte Kategorie ist eine Einreichung an den falschen Schreibtisch.\n\nDer Typblock sind die Felder 5001 bis 5020. Die Anmeldung nach § 14a ab 4,2 kW, und ein Zustimmungsverfahren nach § 19 NAV, wo der Netzbetreiber eines verlangt, folgen aus der deklarierten Leistung und erscheinen als Blocker auf dem Fall.\n\nDie Erkennung läuft für Inline-Material sofort, und die Antwort spiegelt sie bei kleinen Eingaben bereits wider; ein Fall, der noch gelesen wird, kommt als `extracting` zurück und sollte abgefragt oder per Webhook abgewartet werden.\n\nWas entsteht, ist eine gewöhnliche Anmeldung. Alles danach geschieht auf `/v1/anmeldungen/{id}` und ist für jeden Fall gleich, gleich was ihn eröffnet hat.",
        "tags": [
          "Wallboxen und Ladepunkte"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "examples": {
                "fixture": {
                  "summary": "Fixture: eine gehostete Wallbox-Rechnung, kostenlos, kein Modellaufruf",
                  "value": {
                    "external_ref": "ERP-5401",
                    "intent": "draft",
                    "material": [
                      {
                        "fixture": "wallbox-invoice-de"
                      }
                    ]
                  }
                },
                "raw": {
                  "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
                  "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
                  "value": {
                    "external_ref": "ERP-5403",
                    "intent": "draft",
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo,\n\nanbei die Unterlagen zu meiner Wallbox (Easee Home, 22 kW), Rechnung und Typenschild. Die Anlage laeuft seit 2022, Zaehlernummer 1ESY1161234567. Ich bin Eigentuemer. Bitte Modul 1 nach 14a EnWG anmelden.\n\nViele Gruesse\nThomas Weber\nSchackumer Strasse 20, 40667 Meerbusch"
                      },
                      {
                        "kind": "file",
                        "filename": "rechnung-haslinger.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQK"
                      },
                      {
                        "kind": "file",
                        "filename": "typenschild-wallbox.jpg",
                        "content_type": "image/jpeg",
                        "data": "/9j/4AAQSkZJRgAB"
                      }
                    ]
                  }
                },
                "declared": {
                  "summary": "Deklariert: Felder aus Ihrem CRM, 22 kW, steuerbar",
                  "value": {
                    "external_ref": "ERP-5402",
                    "intent": "draft",
                    "declared": {
                      "site": {
                        "street": "Schackumer Strasse",
                        "house_number": "20",
                        "postal_code": "40667",
                        "city": "Meerbusch"
                      },
                      "end_customer": {
                        "first_name": "Thomas",
                        "last_name": "Weber",
                        "email": "t.weber@example.de",
                        "phone": "+49 2150 5550144",
                        "is_owner": true
                      },
                      "installer": {
                        "company": "Elektro Haslinger GmbH",
                        "email": "info@elektro-haslinger.example"
                      },
                      "device": {
                        "count": 1,
                        "manufacturer": "Easee",
                        "model": "Easee Home",
                        "grid_draw_kw": 22,
                        "installed_at": "2026-04-19",
                        "status": "in_operation"
                      },
                      "section_14a": {
                        "is_controllable": true,
                        "control_responsibility": "grid_operator",
                        "modules": "1",
                        "consent": {
                          "given_by": "end_customer",
                          "given_at": "2026-04-15T09:30:00Z",
                          "channel": "email"
                        }
                      },
                      "connection": {
                        "meter_number": "1ESY1161234567",
                        "has_smart_meter": false
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein deklarierter Wert wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "validation_failed": {
                    "$ref": "#/components/examples/ProblemValidation"
                  }
                }
              }
            }
          },
          "402": {
            "description": "Keine Credits mehr. Der Fall wird nicht erstellt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Ratenbegrenzung. Retry-After ist gesetzt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/ladeeinrichtung/upload": {
      "post": {
        "operationId": "uploadLadeeinrichtung",
        "summary": "Dateien hochladen für einen Ladepunkt",
        "description": "Eröffnet den Fall aus Dateien, so wie ein Backoffice sie hat: die Dokumente, die Kundenmail, und welche Felder Sie schon vorliegen haben. `curl -F file=@rechnung.pdf -F file=@typenschild.jpg -F text=<kundenmail.txt` auf diese Route antwortet mit dem neuen Fall und allen dreien unter `material[]`, bereits im Lesen.\n\nEin `file`-Part pro Dokument (ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei), `text` für eine eingefügte Mail, und die Felder des JSON-Bodys als weitere Parts, `declared` als JSON-Text. Was eine Datei ist, ergibt sich aus ihren Bytes, der Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert. Leere Parts werden ignoriert, sodass das Formular, das der Client dieser Seite baut, jede Zeile angehakt, durchgeht.\n\nDer Fall, der zurückkommt, ist derselbe, den `POST /v1/anmeldungen/ladeeinrichtung` liefert, und alles danach ist identisch: lesen, korrigieren, mit `POST /{id}/upload` ergänzen, einreichen. Ein hierher gesendeter JSON-Body ist ein 415, der die Erstellroute benennt; die beiden Türen überlappen nicht, sodass keine eine zweite Form zu lernen hat.",
        "tags": [
          "Wallboxen und Ladepunkte"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "installer_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "file"
                    ],
                    "default": "draft",
                    "description": "Wie im JSON-Body."
                  },
                  "operator": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Wie im JSON-Body."
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das `declared`-Objekt des JSON-Bodys, als JSON-Text."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: zwei Dateien und die Kundenmail",
                  "description": "Wählen Sie die Dateien; die übrigen Parts sind Text. Die Antwort ist der Fall mit allem unter `material[]`, bereits im Lesen.",
                  "value": {
                    "text": "Hallo, anbei Rechnung und Typenschild. Zaehlernummer 1ESY1189004412.",
                    "external_ref": "ERP-9001",
                    "intent": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf die Erstellroute gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein Part wurde abgelehnt, mit seinem Namen.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/ladeeinrichtung/fields": {
      "get": {
        "operationId": "getLadeeinrichtungFieldDictionary",
        "summary": "Felder für einen Ladepunkt",
        "description": "Jedes Feld, das für einen Ladepunkt gilt, mit seiner Feldnummer, der Bezeichnung und dem Hilfetext des Standards, seinem Typ und seinen Optionen, wann es Pflicht ist, und von wem die Antwort erwartet wird.\n\nBereits durch den Pfad eingegrenzt, es gibt also keinen `category`-Parameter, den man falsch setzen könnte. Bauen Sie Ihr Erfassungsformular hieraus, statt unser Schema fest zu verdrahten, und es funktioniert weiter, während das Wörterbuch wächst.",
        "tags": [
          "Wallboxen und Ladepunkte"
        ],
        "parameters": [
          {
            "name": "operator",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
            },
            "description": "Die eigenen Anforderungen dieses Netzbetreibers einschließen, markiert als `operator_specific`. Ohne Wirkung unter einem universellen Schlüssel, der bewusst dieselbe Feldliste sieht, welchen Netzbetreiber er auch nennt; siehe `requirements` auf einem Fall."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Das Wörterbuch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "surface": {
                      "type": "string",
                      "enum": [
                        "universal",
                        "operator"
                      ],
                      "description": "Welche Oberfläche Ihnen geantwortet hat. Zurückgegeben, damit Sie sich nicht merken müssen, welchen Schlüssel Sie benutzt haben."
                    },
                    "operator": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "version",
                    "surface",
                    "data"
                  ]
                },
                "examples": {
                  "slice": {
                    "$ref": "#/components/examples/FieldDictionarySlice"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/klimaanlage": {
      "post": {
        "operationId": "createKlimaanlage",
        "summary": "Eine Klimaanlage anmelden",
        "description": "**Drei Bodys zum Ausprobieren.** Unter *Request Example* rechts wechselt die Auswahl neben *Test Request* zwischen einem **Fixture**, das die Sandbox kostenlos liest, der **rohen** Form (die Kundenmail und zwei Dateien, nichts Strukturiertes) und **deklarierten** Feldern aus Ihrem CRM. Was dort gewählt ist, öffnet *Test Request*; im Client liegen dieselben Bodys in der Seitenleiste unter dieser Anfrage. Eigene Dateien gehen an *Dateien hochladen*, die Operation direkt nach dieser, die sie als Formular mit Dateiauswahl entgegennimmt.\n\nEröffnet einen Fall für eine Anlage zur Raumkühlung, und mit ihr die Anmeldung nach § 14a als steuerbare Verbrauchseinrichtung, wo die installierte Leistung hinter dem Anschluss 4,2 kW übersteigt.\n\n**Der Pfad legt `device.category` auf `air_conditioning` fest.** Lassen Sie das Feld weg; ein anderer Wert ist ein 400 statt eines stillen Überschreibens.\n\n88 Felder im Umfang, 58 Pflicht. Der Typblock sind die Felder 7001 bis 7017, und es ist das vierte und letzte § 14a-Gerät. Beachten Sie, dass der Standard Hersteller und Typ einer Klimaanlage NICHT zur Pflicht macht, wie er es bei einem Ladepunkt tut; hier blockiert also keines von beiden.\n\n`section_14a.critical_infrastructure` (Feld 7006) blockiert. Eine Anlage, die betriebsnotwendigen Zwecken oder kritischer Infrastruktur dient, darf der Netzbetreiber nicht dimmen, und eine Einreichung ohne diese Antwort würde im Namen des Kunden einer Steuerung zustimmen, die er ablehnen darf.\n\nWas entsteht, ist eine gewöhnliche Anmeldung, gelesen und eingereicht auf `/v1/anmeldungen/{id}` wie jede andere.",
        "tags": [
          "Klimaanlagen"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "examples": {
                "fixture": {
                  "summary": "Fixture: eine gehostete Klimaanlagenrechnung, kostenlos, kein Modellaufruf",
                  "value": {
                    "external_ref": "ERP-5501",
                    "intent": "draft",
                    "material": [
                      {
                        "fixture": "air-conditioning-invoice-de"
                      }
                    ]
                  }
                },
                "raw": {
                  "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
                  "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
                  "value": {
                    "external_ref": "ERP-5503",
                    "intent": "draft",
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo,\n\nanbei Rechnung und Typenschild zu unserer neuen Klimaanlage (Daikin Perfera FTXM50R, 1,6 kW, Split). Zaehlernummer 1ESY1130077890. Keine kritische Infrastruktur, bitte Modul 1 nach 14a EnWG anmelden.\n\nViele Gruesse\nMaria Brandt\nAm Winterhafen 12, 55131 Mainz"
                      },
                      {
                        "kind": "file",
                        "filename": "rechnung-klimatechnik.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQK"
                      },
                      {
                        "kind": "file",
                        "filename": "typenschild-klimageraet.jpg",
                        "content_type": "image/jpeg",
                        "data": "/9j/4AAQSkZJRgAB"
                      }
                    ]
                  }
                },
                "declared": {
                  "summary": "Deklariert: Felder aus Ihrem CRM, 1,6 kW, keine kritische Infrastruktur",
                  "value": {
                    "external_ref": "ERP-5502",
                    "intent": "draft",
                    "declared": {
                      "site": {
                        "street": "Am Winterhafen",
                        "house_number": "12",
                        "postal_code": "55131",
                        "city": "Mainz"
                      },
                      "end_customer": {
                        "first_name": "Maria",
                        "last_name": "Brandt",
                        "email": "m.brandt@example.de",
                        "phone": "+49 6131 5550166",
                        "is_owner": true
                      },
                      "installer": {
                        "company": "Klimatechnik Rhein-Main GmbH",
                        "email": "service@klimatechnik-rhein-main.example"
                      },
                      "device": {
                        "count": 1,
                        "manufacturer": "Daikin",
                        "model": "Perfera FTXM50R",
                        "electrical_power_kw": 1.6,
                        "starting_current_a": 8.5,
                        "grid_draw_kw": 1.6,
                        "installed_at": "2026-08-05",
                        "status": "planned"
                      },
                      "section_14a": {
                        "is_controllable": true,
                        "critical_infrastructure": false,
                        "control_responsibility": "metering_operator",
                        "modules": "1",
                        "consent": {
                          "given_by": "end_customer",
                          "given_at": "2026-08-01T09:30:00Z",
                          "channel": "email"
                        }
                      },
                      "connection": {
                        "meter_number": "1ESY1130077890"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein deklarierter Wert wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "validation_failed": {
                    "$ref": "#/components/examples/ProblemValidation"
                  }
                }
              }
            }
          },
          "402": {
            "description": "Keine Credits mehr. Der Fall wird nicht erstellt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Ratenbegrenzung. Retry-After ist gesetzt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/klimaanlage/upload": {
      "post": {
        "operationId": "uploadKlimaanlage",
        "summary": "Dateien hochladen für eine Klimaanlage",
        "description": "Eröffnet den Fall aus Dateien, so wie ein Backoffice sie hat: die Dokumente, die Kundenmail, und welche Felder Sie schon vorliegen haben. `curl -F file=@rechnung.pdf -F file=@typenschild.jpg -F text=<kundenmail.txt` auf diese Route antwortet mit dem neuen Fall und allen dreien unter `material[]`, bereits im Lesen.\n\nEin `file`-Part pro Dokument (ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei), `text` für eine eingefügte Mail, und die Felder des JSON-Bodys als weitere Parts, `declared` als JSON-Text. Was eine Datei ist, ergibt sich aus ihren Bytes, der Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert. Leere Parts werden ignoriert, sodass das Formular, das der Client dieser Seite baut, jede Zeile angehakt, durchgeht.\n\nDer Fall, der zurückkommt, ist derselbe, den `POST /v1/anmeldungen/klimaanlage` liefert, und alles danach ist identisch: lesen, korrigieren, mit `POST /{id}/upload` ergänzen, einreichen. Ein hierher gesendeter JSON-Body ist ein 415, der die Erstellroute benennt; die beiden Türen überlappen nicht, sodass keine eine zweite Form zu lernen hat.",
        "tags": [
          "Klimaanlagen"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "installer_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "file"
                    ],
                    "default": "draft",
                    "description": "Wie im JSON-Body."
                  },
                  "operator": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Wie im JSON-Body."
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das `declared`-Objekt des JSON-Bodys, als JSON-Text."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: zwei Dateien und die Kundenmail",
                  "description": "Wählen Sie die Dateien; die übrigen Parts sind Text. Die Antwort ist der Fall mit allem unter `material[]`, bereits im Lesen.",
                  "value": {
                    "text": "Hallo, anbei Rechnung und Typenschild. Zaehlernummer 1ESY1189004412.",
                    "external_ref": "ERP-9001",
                    "intent": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf die Erstellroute gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein Part wurde abgelehnt, mit seinem Namen.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/klimaanlage/fields": {
      "get": {
        "operationId": "getKlimaanlageFieldDictionary",
        "summary": "Felder für eine Klimaanlage",
        "description": "Jedes Feld, das für eine Klimaanlage gilt, mit seiner Feldnummer, der Bezeichnung und dem Hilfetext des Standards, seinem Typ und seinen Optionen, wann es Pflicht ist, und von wem die Antwort erwartet wird.\n\nBereits durch den Pfad eingegrenzt, es gibt also keinen `category`-Parameter, den man falsch setzen könnte.",
        "tags": [
          "Klimaanlagen"
        ],
        "parameters": [
          {
            "name": "operator",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
            },
            "description": "Die eigenen Anforderungen dieses Netzbetreibers einschließen, markiert als `operator_specific`. Ohne Wirkung unter einem universellen Schlüssel, der bewusst dieselbe Feldliste sieht, welchen Netzbetreiber er auch nennt; siehe `requirements` auf einem Fall."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Das Wörterbuch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "surface": {
                      "type": "string",
                      "enum": [
                        "universal",
                        "operator"
                      ],
                      "description": "Welche Oberfläche Ihnen geantwortet hat. Zurückgegeben, damit Sie sich nicht merken müssen, welchen Schlüssel Sie benutzt haben."
                    },
                    "operator": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "version",
                    "surface",
                    "data"
                  ]
                },
                "examples": {
                  "slice": {
                    "$ref": "#/components/examples/FieldDictionarySlice"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/14a": {
      "post": {
        "operationId": "create14a",
        "summary": "Eine § 14a-Anmeldung erstellen",
        "description": "**Vier Bodys zum Ausprobieren.** Unter *Request Example* rechts wechselt die Auswahl neben *Test Request* zwischen einem **Fixture**, das die Sandbox kostenlos liest, der **rohen** Form (die Kundenmail und zwei Dateien, nichts Strukturiertes), **deklarierten** Feldern aus Ihrem CRM, und **gemischt**, von beidem etwas. Was dort gewählt ist, öffnet *Test Request*; im Client liegen dieselben Bodys in der Seitenleiste unter dieser Anfrage. Eigene Dateien gehen an *Dateien hochladen*, die Operation direkt nach dieser, die sie als Formular mit Dateiauswahl entgegennimmt.\n\nErstellt einen Fall aus deklarierten Feldern, rohem Material, oder beidem. Die Erkennung läuft für Inline-Material sofort, und die Antwort spiegelt sie bei kleinen Eingaben bereits wider; ein Fall, dessen Material noch gelesen wird, kommt als `extracting` zurück und sollte abgefragt oder per Webhook abgewartet werden.\n\nDies ist der einzige § 14a-spezifische Pfad. Was entsteht, ist eine gewöhnliche Anmeldung: lesen, korrigieren, Material ergänzen und einreichen über `/v1/anmeldungen/{id}`, genau wie bei einem Fall, der auf jede andere Weise erstellt wurde.",
        "tags": [
          "§ 14a, beliebiges Gerät"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRequest"
              },
              "examples": {
                "fixture": {
                  "summary": "Fixture: eine gehostete Rechnung, kostenlos, kein Modellaufruf",
                  "description": "Senden Sie diesen zuerst. `fixture` benennt ein Dokument, das wir hosten und bereits gelesen haben; es wird also nichts hochgeladen und kein Modellaufruf für das Lesen berechnet, und ein Bildschirm im Bau kann den ganzen Tag dagegen neu bauen.\n\nDas Ergebnis ist ein echter Fall in der Sandbox-Umgebung, mit denselben Feldern, denselben Zitaten und derselben Form, die ein Live-Aufruf liefert. Nichts daran kann einreichen, berechnen oder jemanden anmailen.",
                  "value": {
                    "external_ref": "ERP-4711",
                    "intent": "draft",
                    "material": [
                      {
                        "fixture": "wallbox-invoice-de"
                      }
                    ]
                  }
                },
                "raw": {
                  "summary": "Roh: alles ablegen, nichts deklarieren",
                  "description": "Der ganze Sinn des rohen Kanals: das Backoffice eines Installateurs hat eine Kundenmail und drei PDFs und nichts Strukturiertes. Jedes Feld der Antwort ist dann `source: \"extracted\"`, jedes mit dem Zitat, aus dem es gelesen wurde.\n\nFunktioniert auf beiden Umgebungen. Ein von Ihnen geliefertes Dokument zu lesen kostet einen Modellaufruf pro Datei, weshalb die Sandbox höchstens 25 echte Dokumente pro Konto und Tag liest; Fixtures sind dort unbegrenzt.\n\nDie beiden `data`-Werte sind ein PDF-Header und nichts danach: gültiges base64, die Anfrage ist also sendbar, und viel zu wenig für ein Dokument. Bis zum 2026-09-06 trugen sie ein abschließendes `…`, wodurch dieses Beispiel an der Kodierung scheiterte, sobald jemand Send drückte.",
                  "value": {
                    "external_ref": "ERP-4711",
                    "intent": "draft",
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo, anbei die Unterlagen zu meiner Wallbox. Die Anlage laeuft seit 2022. Ich bin Eigentuemer. Bitte Modul 1 nach 14a EnWG anmelden.\n\nViele Gruesse\nAlexander Bergmann"
                      },
                      {
                        "kind": "file",
                        "filename": "technische-zustimmung.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQKJeLjz9MK"
                      },
                      {
                        "kind": "file",
                        "filename": "schlussrechnung.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQKJcfsj6IK"
                      }
                    ]
                  }
                },
                "declared": {
                  "summary": "Deklariert: alles deklarieren, nichts ablegen",
                  "description": "Ein ERP, das die Daten schon hat. Keine Erkennung läuft, der Fall ist beim ersten Aufruf `ready`, und `intent: \"file\"` reicht ihn in einem Durchgang ein.",
                  "value": {
                    "external_ref": "ERP-4712",
                    "intent": "file",
                    "declared": {
                      "site": {
                        "street": "Ahornweg",
                        "house_number": "7a",
                        "postal_code": "22607",
                        "city": "Hamburg"
                      },
                      "end_customer": {
                        "first_name": "Alexander",
                        "last_name": "Bergmann",
                        "email": "a.bergmann@example.de",
                        "phone": "+49 40 5550123",
                        "is_owner": true
                      },
                      "installer": {
                        "company": "Nordlicht Elektrotechnik GmbH",
                        "contact_first_name": "Jan",
                        "contact_last_name": "Petersen",
                        "email": "petersen@example.de",
                        "registry_number": "HH-EL-04127"
                      },
                      "device": {
                        "category": "charge_point",
                        "manufacturer": "ABL",
                        "model": "eMH3 3W2250",
                        "count": 1,
                        "grid_draw_kw": 11,
                        "installed_at": "2022-04-19",
                        "status": "in_operation"
                      },
                      "connection": {
                        "meter_number": "1ISK0000123456",
                        "has_smart_meter": true
                      },
                      "section_14a": {
                        "modules": "1",
                        "control_responsibility": "metering_operator",
                        "consent": {
                          "given_by": "end_customer",
                          "given_at": "2026-08-17T14:05:00Z",
                          "channel": "phone",
                          "reference": "call-2026-08-17-1405"
                        }
                      }
                    }
                  }
                },
                "mixed": {
                  "summary": "Gemischt: einige Felder deklariert, der Rest in einem PDF",
                  "description": "Einige Felder sind bekannt, der Rest steht in einem PDF. Deklarierte Werte werden von der Erkennung nie überschrieben; wo die Erkennung widerspricht, wird der Widerspruch in `conflicts` gemeldet, statt still aufgelöst zu werden.",
                  "value": {
                    "external_ref": "ERP-4713",
                    "intent": "draft",
                    "declared": {
                      "site": {
                        "street": "Ahornweg",
                        "house_number": "7a",
                        "postal_code": "22607",
                        "city": "Hamburg"
                      },
                      "end_customer": {
                        "first_name": "Alexander",
                        "last_name": "Bergmann",
                        "email": "a.bergmann@example.de"
                      },
                      "device": {
                        "category": "charge_point"
                      },
                      "section_14a": {
                        "modules": "1",
                        "consent": {
                          "given_by": "end_customer",
                          "given_at": "2026-08-17T14:05:00Z",
                          "channel": "phone",
                          "reference": "call-2026-08-17-1405"
                        }
                      },
                      "nr": {
                        "5017": "11"
                      }
                    },
                    "material": [
                      {
                        "kind": "file",
                        "storage_id": "kg21x9…",
                        "filename": "schlussrechnung.pdf",
                        "content_type": "application/pdf"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein deklarierter Wert wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "validation_failed": {
                    "$ref": "#/components/examples/ProblemValidation"
                  }
                }
              }
            }
          },
          "402": {
            "description": "Keine Credits mehr. Der Fall wird nicht erstellt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Ratenbegrenzung. Retry-After ist gesetzt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/14a/upload": {
      "post": {
        "operationId": "upload14a",
        "summary": "Dateien hochladen für eine § 14a-Anmeldung",
        "description": "Eröffnet den Fall aus Dateien, so wie ein Backoffice sie hat: die Dokumente, die Kundenmail, und welche Felder Sie schon vorliegen haben. `curl -F file=@rechnung.pdf -F file=@typenschild.jpg -F text=<kundenmail.txt` auf diese Route antwortet mit dem neuen Fall und allen dreien unter `material[]`, bereits im Lesen.\n\nEin `file`-Part pro Dokument (ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei), `text` für eine eingefügte Mail, und die Felder des JSON-Bodys als weitere Parts, `declared` als JSON-Text. Was eine Datei ist, ergibt sich aus ihren Bytes, der Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert. Leere Parts werden ignoriert, sodass das Formular, das der Client dieser Seite baut, jede Zeile angehakt, durchgeht.\n\nDer Fall, der zurückkommt, ist derselbe, den `POST /v1/anmeldungen/14a` liefert, und alles danach ist identisch: lesen, korrigieren, mit `POST /{id}/upload` ergänzen, einreichen. Ein hierher gesendeter JSON-Body ist ein 415, der die Erstellroute benennt; die beiden Türen überlappen nicht, sodass keine eine zweite Form zu lernen hat.",
        "tags": [
          "§ 14a, beliebiges Gerät"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  },
                  "external_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "installer_ref": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Wie im JSON-Body."
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "file"
                    ],
                    "default": "draft",
                    "description": "Wie im JSON-Body."
                  },
                  "operator": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
                    "description": "Wie im JSON-Body."
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das `declared`-Objekt des JSON-Bodys, als JSON-Text."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: zwei Dateien und die Kundenmail",
                  "description": "Wählen Sie die Dateien; die übrigen Parts sind Text. Die Antwort ist der Fall mit allem unter `material[]`, bereits im Lesen.",
                  "value": {
                    "text": "Hallo, anbei Rechnung und Typenschild. Zaehlernummer 1ESY1189004412.",
                    "external_ref": "ERP-9001",
                    "intent": "draft"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf die Erstellroute gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein Part wurde abgelehnt, mit seinem Namen.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/14a/fields": {
      "get": {
        "operationId": "get14aFieldDictionary",
        "summary": "Das Feldwörterbuch",
        "description": "Jedes Feld, das dieser Endpunkt versteht, mit seiner Feldnummer, der Bezeichnung und dem Hilfetext des Standards, seinem Typ und seinen Optionen, wann es Pflicht ist, und von wem die Antwort erwartet wird. Bauen Sie Ihr Erfassungsformular hieraus, statt unser Schema fest zu verdrahten, und es funktioniert weiter, während das Wörterbuch wächst.",
        "tags": [
          "§ 14a, beliebiges Gerät"
        ],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "charge_point",
                "heat_pump"
              ]
            },
            "description": "Auf die Felder einer Gerätefamilie beschränken."
          },
          {
            "name": "operator",
            "in": "query",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,62}$"
            },
            "description": "Die eigenen Anforderungen dieses Netzbetreibers einschließen, markiert als `operator_specific`. Ohne Wirkung unter einem universellen Schlüssel, der bewusst dieselbe Feldliste sieht, welchen Netzbetreiber er auch nennt; siehe `requirements` auf einem Fall."
          },
          {
            "name": "lang",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "de",
                "en"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Das Wörterbuch.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "surface": {
                      "type": "string",
                      "enum": [
                        "universal",
                        "operator"
                      ],
                      "description": "Welche Oberfläche Ihnen geantwortet hat. Zurückgegeben, damit Sie sich nicht merken müssen, welchen Schlüssel Sie benutzt haben."
                    },
                    "operator": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  },
                  "required": [
                    "version",
                    "surface",
                    "data"
                  ]
                },
                "examples": {
                  "slice": {
                    "$ref": "#/components/examples/FieldDictionarySlice"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen": {
      "get": {
        "operationId": "listAnmeldungen",
        "summary": "Ihre Fälle auflisten",
        "description": "Zusammenfassungszeilen, neueste zuerst, damit ein nächtlicher Abgleich günstig bleibt. Die Details eines Falls (was aufgelöst, offen oder widersprüchlich ist) stehen auf dem Fall selbst, denn sie für jede Zeile zurückzugeben machte den häufigen Aufruf zum teuren.\n\nEine Seite enthält einen Modus. Ein Live-Schlüssel sieht Live-Fälle und ein Sandbox-Schlüssel Sandbox-Fälle; es gibt keinen Parameter, sie zu mischen, denn eine Seite, die das täte, brächte Testdaten auf einen Produktionsbildschirm.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Aus `next_cursor` einer vorherigen Seite."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eine Seite Fälle.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mode": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ]
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "external_ref": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "anm_7Kq3ZmR8vT",
                      "external_ref": "ERP-4711",
                      "status": "needs_input",
                      "label": "Alexander Bergmann, Ahornweg 7a",
                      "created_at": "2026-08-30T09:12:04Z",
                      "updated_at": "2026-08-30T09:12:41Z"
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}": {
      "get": {
        "operationId": "getAnmeldung",
        "summary": "Einen Fall lesen",
        "description": "Aktueller Stand. Für die Historie den Events-Endpunkt verwenden.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          }
        ],
        "responses": {
          "200": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Kein solcher Fall.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateAnmeldung",
        "summary": "Werte deklarieren oder korrigieren",
        "description": "Der feinkörnige Kanal nach dem Erstellen. Alles hier Deklarierte gewinnt gegen alles Ausgelesene, und so wird auch ein Konflikt aufgelöst.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchRequest"
              },
              "examples": {
                "resolve_conflict": {
                  "$ref": "#/components/examples/PatchResolveConflict"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Der Fall ist bereits eingereicht und kann nicht mehr bearbeitet werden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Ein deklarierter Wert wurde abgelehnt.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "discardAnmeldung",
        "summary": "Einen Entwurf verwerfen",
        "description": "Nur ein Fall, der nicht eingereicht wurde. Eingereichte Fälle werden zurückgezogen, nicht gelöscht.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          }
        ],
        "responses": {
          "204": {
            "description": "Verworfen."
          },
          "409": {
            "description": "Bereits eingereicht.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/material": {
      "post": {
        "operationId": "addMaterial",
        "summary": "Rohes Material ergänzen",
        "description": "Ergänzt einen bestehenden Fall und beginnt, es zu lesen. Bereits deklarierte Werte bleiben unberührt; neue Erkennungen können Felder öffnen oder schließen und Konflikte auslösen.\n\nEigene Dateien gehen als Formular an `POST /{id}/upload`, so wie `curl -F file=@nachtrag.pdf` sie sendet. Hier, in JSON, reist eine Datei inline als base64-`data`, bis 8 MB, oder als die `storage_id`, die ein Upload-Ziel nach `POST /{id}/documents` zurückgegeben hat. Siehe die Übersicht, Ein Dokument senden.\n\nErlaubt auf jedem nicht eingereichten Fall in jedem `status`: `needs_input`, `extracting` oder `ready`. Das neue Material wird auf dieselbe Weise gelesen und der Fall neu aufgelöst, sodass ein `ready`-Fall wieder auf `needs_input` zurückfallen kann, wenn das neue Dokument etwas widerspricht. Ein eingereichter Fall antwortet mit 409.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "material": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/Material"
                    },
                    "minItems": 1,
                    "maxItems": 30
                  }
                },
                "required": [
                  "material"
                ]
              },
              "examples": {
                "uploaded": {
                  "summary": "Hochgeladen: die Speicher-ID, mit der ein Upload-Ziel geantwortet hat",
                  "value": {
                    "material": [
                      {
                        "kind": "file",
                        "storage_id": "kg21x9q8v0s5c7b2m4n6p8r0t2w4y6a",
                        "filename": "rechnung.pdf",
                        "content_type": "application/pdf"
                      }
                    ]
                  }
                },
                "inline": {
                  "summary": "Inline: eine kleine Datei als base64 in `data`",
                  "description": "Das `data` hier ist ein PDF-Header und nichts danach: gültiges base64, viel zu wenig für ein Dokument.",
                  "value": {
                    "material": [
                      {
                        "kind": "file",
                        "filename": "typenschild.pdf",
                        "content_type": "application/pdf",
                        "data": "JVBERi0xLjQK"
                      }
                    ]
                  }
                },
                "text": {
                  "summary": "Text: eine eingefügte Kundenmail",
                  "value": {
                    "material": [
                      {
                        "kind": "text",
                        "label": "Kundenmail",
                        "content": "Hallo, anbei die Unterlagen zur PV-Anlage. Die Module sind seit Juni montiert.\n\nViele Gruesse\nInes Kaufmann"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Material zu groß.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/upload": {
      "post": {
        "operationId": "uploadToAnmeldung",
        "summary": "Dateien zu einem Fall hochladen",
        "description": "Fügt einem bestehenden Fall Dateien hinzu und beginnt, sie zu lesen. `curl -F file=@nachtrag.pdf` auf diese Route ist der ganze Aufruf; `text` trägt eine eingefügte Mail. Bis 25 MB pro Datei; was eine Datei ist, ergibt sich aus ihren Bytes. Bereits deklarierte Werte bleiben unberührt; neue Erkennungen können Felder öffnen oder schließen und Konflikte auslösen.\n\nErlaubt auf jedem nicht eingereichten Fall in jedem `status`, `ready` eingeschlossen; der Fall wird neu aufgelöst, sobald das neue Material gelesen ist. Ein eingereichter Fall antwortet mit 409.\n\nDer JSON-Weg hinein ist `POST /{id}/material`, für eine Datei inline als base64 oder per Speicher-ID; ein hierher gesendeter JSON-Body ist ein 415, der ihn benennt.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Ein Part pro Dokument, für jede Datei wiederholt: ein PDF, ein JPEG, ein PNG, ein WebP oder ein HEIC-Bild, bis 25 MB je Datei. Was eine Datei ist, ergibt sich aus ihren Bytes, der eigene Medientyp des Parts spielt also keine Rolle; eine Datei jeder anderen Art ist ein 415, der sie benennt, und von der Anfrage wird nichts gespeichert."
                  },
                  "text": {
                    "type": "string",
                    "description": "Eine eingefügte Mail oder eine Notiz, gelesen wie eine Datei. `curl -F text=<kundenmail.txt` sendet eine."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf, image/jpeg, image/png, image/webp, image/heic"
                }
              },
              "examples": {
                "multipart": {
                  "summary": "Multipart: eine dem Fall hinzugefügte Datei",
                  "description": "Wählen Sie die Datei. `curl -F file=@nachtrag.pdf` auf diese Route tut dasselbe.",
                  "value": {}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Eine Datei über 25 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Eine Datei, die die Erkennung nicht annehmen kann, benannt; oder ein JSON-Body, der auf /material gehört.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/documents": {
      "post": {
        "operationId": "createUploadTarget",
        "summary": "Ein Upload-Ziel holen",
        "description": "Für einen Client, der Bytes streamt und die Datei nie in der Hand hält. Alle anderen senden die Datei selbst, als Formular, an `POST /v1/anmeldungen/{typ}/upload` oder `POST /{id}/upload`, in einem Aufruf.\n\nDrei Aufrufe. Hier ein Ziel anfordern, die rohen Bytes per POST an `upload_url` senden, mit dem eigenen `Content-Type` der Datei und ohne Bearer (das Ziel ist die Berechtigung, einmal verwendbar, läuft innerhalb einer Stunde ab), und der Speicher antwortet `{\"storageId\": \"...\"}`. Senden Sie das als `storage_id` auf `POST /{id}/material`, und der Fall liest es. Das SDK macht die drei Aufrufe mit `cases.uploadByTarget` für Sie.\n\nNur das Ziel kommt zurück. Ein früherer Entwurf dieses Vertrags gab auch eine `document_id` zurück, was bedeutet hätte, eine Dokumentzeile zu schreiben, bevor die Bytes existierten: kein Speicherschlüssel, kein Medientyp, keine Größe: genau die Felder, die das Lesen aus dem Fallspeicher sicher machen. Die ID, die ein Aufrufer braucht, ist die, die ihm der Upload selbst gibt.\n\nDer Besitz wird geprüft, bevor ein Ziel ausgegeben wird. Eine Upload-URL für den Fall eines anderen wäre ein Weg, ihm Dokumente anzuhängen.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          }
        ],
        "responses": {
          "201": {
            "description": "Ein kurzlebiges Upload-Ziel.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upload_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "required": [
                    "upload_url"
                  ]
                },
                "example": {
                  "upload_url": "https://…/api/storage/upload?token=…"
                }
              }
            }
          },
          "404": {
            "description": "Kein solcher Fall, oder nicht Ihrer.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Der Fall ist bereits eingereicht.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/file": {
      "post": {
        "operationId": "fileAnmeldung",
        "summary": "Die Anmeldung einreichen",
        "description": "Bindet den Fall verbindlich und berechnet den Credit. Ist etwas Blockierendes offen oder widersprüchlich, kommt der Fall unverändert als `needs_input` mit einem 409 zurück, und kein Credit wird berechnet.\n\n**Es unterschreibt nicht, und kein Feld hier kann das bewirken.** Zwei Unterschriften auf dem § 14a-Formular sind nie unsere. Das Einreichen erzeugt das Dokument und mailt dem Endkunden mit der Bitte um Unterschrift, von der eigenen verifizierten Domain des Installateurs, wo es eine gibt, mit Antwort an ein Postfach, das wir lesen, damit das unterschriebene Formular zum Fall zurückkommt. Beobachten Sie `signature` auf dem Fall, um zu sehen, wo das steht.\n\nDie Weiterleitung geschieht nach bestem Bemühen: ein Fall ohne bereits ausgefülltes Formular, ohne Kundenadresse oder ohne betriebenes Postfach wird trotzdem eingereicht, und `signature` bleibt null, bis jemand es vervollständigt. Die Einreichung deswegen zu verweigern, ließe diesen Aufruf aus Gründen scheitern, die mit dem Einreichen nichts zu tun haben.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sandbox_outcome": {
                    "type": "string",
                    "enum": [
                      "accepted",
                      "rejected",
                      "needs_more_information"
                    ],
                    "default": "accepted",
                    "description": "NUR SANDBOX. Was der Netzbetreiber entscheidet. Würde die Sandbox immer annehmen, baute niemand den Ablehnungspfad; also wählen Sie, und der Fall landet in derselben Stufe wie ein Live-Fall, mit einem genannten Grund.\n\nUnter einem Live-Schlüssel ist dies ein 422, der das Feld benennt, kein Ignorieren. Das Ergebnis einer Live-Einreichung kommt vom Netzbetreiber."
                  },
                  "sandbox_pace": {
                    "type": "string",
                    "enum": [
                      "instant",
                      "staged"
                    ],
                    "default": "instant",
                    "description": "NUR SANDBOX. Wie schnell der Sandbox-Netzbetreiber antwortet.\n\n`instant` antwortet innerhalb des Einreichaufrufs, was eine Testsuite will. `staged` ist das eine, was eine sofortige Antwort nicht zeigen kann: ein Live-Fall wird eingereicht, wartet und wird später beantwortet. Staged durchläuft `submitted`, `waiting_operator` und dann `sandbox_outcome` im Abstand weniger Sekunden auf einem Timer, jeder Schritt ein `case.status_changed`-Ereignis, das Ihr Webhook der Reihe nach erhält, sodass ein Empfänger, der die Antwort bisher nur innerhalb des Einreichaufrufs gesehen hat, das Warten testen kann.\n\nUnter einem Live-Schlüssel ist dies ein 422, der das Feld benennt. Eine Live-Einreichung wird beantwortet, wenn der Netzbetreiber antwortet."
                  },
                  "declarations": {
                    "type": "string",
                    "enum": [
                      "release",
                      "withhold"
                    ],
                    "default": "release",
                    "description": "Was diese Übergabe mit den Standarderklärungen des Netzbetreibers tut: die Bestätigung, dass seine Technischen Anschlussbedingungen eingehalten werden, und die Erklärung zu seinen Datenschutzhinweisen.\n\n`release`, die Vorgabe, gibt sie mit der Übergabe frei, wo das Konto die AGB in der Fassung vom 28.09.2026 oder einer späteren akzeptiert hat, deren § 3 Abs. 3 das vorsieht, und tut unter einer früheren Fassung nichts. `withhold` hält sie für diesen Fall offen, für einen, für den Sie sie noch nicht abgeben können; sie werden dann wie jede andere offene Frage erfragt. Keine andere Einwilligung, die ein Netzbetreiber verlangt, etwa eine Vollmacht oder die Erlaubnis eines Grundstückseigentümers, wird je auf diesem Weg gesetzt."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Der Fall, wie er jetzt steht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Case"
                },
                "examples": {
                  "needs_input": {
                    "$ref": "#/components/examples/CaseNeedsInput"
                  },
                  "ready": {
                    "$ref": "#/components/examples/CaseReady"
                  },
                  "filed": {
                    "$ref": "#/components/examples/CaseFiled"
                  }
                }
              }
            }
          },
          "402": {
            "description": "Keine Credits mehr. Nie in der Sandbox: ein Sandbox-Fall verbraucht nichts.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Blockierende Felder sind offen, oder der Fall ist bereits eingereicht.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "`sandbox_outcome` oder `sandbox_pace` unter einem Live-Schlüssel, oder ein Wert, den niemand definiert hat.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "Das Protokoll abspielen",
        "description": "Alles, was diesem Fall geschehen ist, älteste zuerst: er wurde erstellt, ein Dokument wurde fertig gelesen, Werte wurden deklariert, er wurde eingereicht, sein Status hat sich bewegt.\n\n**Fragen Sie `case.extraction_completed` ab.** Ein Dokument zu lesen ist der langsame Schritt, Sekunden für eine Notiz und etwa eine Minute für ein einseitiges PDF, mehr für einen Scan oder ein langes, und es ist der Schritt, nach dem sich ein erneutes Lesen des Falls lohnt. Alles andere am Fall bekommen Sie direkt aus `GET /v1/anmeldungen/{id}`.\n\nBehalten Sie die `id` des letzten Ereignisses, das Sie gesehen haben, und übergeben Sie sie als `after`; ein leeres `data` heißt, seitdem ist nichts geschehen. Ein Cursor statt eines Zeitstempels, denn zwei in einer Mutation geschriebene Ereignisse teilen sich eine Millisekunde, und ein zeitbasierter Cursor würde eines wiederholen oder eines auslassen.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Eine Ereignis-`id` einer früheren Seite. Alles danach, exklusiv. Eine ID eines anderen Falls wird abgelehnt."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "description": "Ereignisse pro Seite."
          }
        ],
        "responses": {
          "200": {
            "description": "Ereignisse, älteste zuerst.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "at",
                          "type"
                        ],
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Als `after` zurückgeben, um zu blättern."
                          },
                          "at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "type": {
                            "type": "string",
                            "enum": [
                              "case.created",
                              "case.material_read",
                              "case.extraction_completed",
                              "case.updated",
                              "case.status_changed",
                              "case.filed",
                              "case.information_requested",
                              "case.information_response_received",
                              "case.information_response_ready",
                              "case.information_response_submitted",
                              "case.operator_login_missing"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "description": "Vorhanden auf `case.status_changed` und `case.filed`."
                          },
                          "note": {
                            "type": "string",
                            "description": "Kundentaugliche Formulierung, wo die Statusänderung eine hat."
                          },
                          "material": {
                            "type": "string",
                            "description": "Welches Dokument. Nur `case.material_read`."
                          },
                          "material_status": {
                            "type": "string",
                            "enum": [
                              "read",
                              "failed"
                            ]
                          },
                          "extracted_chars": {
                            "type": "integer"
                          },
                          "information_request": {
                            "description": "Vorhanden bei `case.information_requested`: der unveränderliche Snapshot wird auch im Webhook geliefert.",
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/InformationRequestSnapshot"
                              }
                            ]
                          },
                          "information_response_id": {
                            "type": "string",
                            "description": "Vorhanden bei Lifecycle-Ereignissen der Informationsantwort."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/information-requests/{requestId}": {
      "get": {
        "operationId": "getInformationRequest",
        "summary": "Eine Informationsanforderung des Netzbetreibers lesen",
        "description": "Die kanonische aktuelle Anforderung nach einem `case.information_requested`-Webhook. Der Webhook enthält einen kompakten unveränderlichen `missing`-Snapshot, damit eine Integration ihren Endkunden sofort benachrichtigen kann; lesen Sie diese Ressource erneut, bevor Sie ein Formular zeigen oder eine Antwort annehmen. Die `sequence` der Anforderung ist nur anhängend: Eine weitere Rückfrage des Netzbetreibers erzeugt eine neue Anforderung, statt die Bedeutung der ersten zu ändern.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          },
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die `information_request.id` aus dem Webhook, auf der Umgebung dieses Schlüssels."
          }
        ],
        "responses": {
          "200": {
            "description": "Die angeforderten fehlenden Felder und Dokumente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InformationRequest"
                }
              }
            }
          },
          "404": {
            "description": "Keine solche Anforderung auf diesem Fall und dieser Umgebung.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/information-requests/{requestId}/responses": {
      "post": {
        "operationId": "createInformationResponse",
        "summary": "Die angeforderten Angaben bereitstellen",
        "description": "Erstellt ein unveränderliches Antwortpaket. Verwenden Sie einen `Idempotency-Key`: Ein erneuter Versuch mit demselben Body gibt dieselbe Antwort zurück, ein anderer Body wird abgelehnt. Dokumente werden gelesen, bevor die Antwort `ready_to_submit` wird; warten Sie auf `case.information_response_ready` und rufen Sie dann `/submit` auf.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          },
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die `information_request.id` aus dem Webhook, auf der Umgebung dieses Schlüssels."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InformationResponseRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Das unveränderliche Antwortpaket.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InformationResponse"
                }
              }
            }
          },
          "409": {
            "description": "Der Idempotenzschlüssel wurde mit einem anderen Body wiederverwendet.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Die Anforderung ist geschlossen, unvollständig oder enthält Felder, nach denen sie nicht gefragt hat.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/information-requests/{requestId}/responses/upload": {
      "post": {
        "operationId": "uploadInformationResponse",
        "summary": "Angeforderte Dokumente bereitstellen",
        "description": "Das Datei-Formularäquivalent zu POST /responses. Senden Sie `file`-Teile, optional `text` und `declared` als JSON-Text. Es hat denselben Idempotenz- und Verarbeitungslebenszyklus.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          },
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die `information_request.id` aus dem Webhook, auf der Umgebung dieses Schlüssels."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Derselbe Schlüssel mit demselben Body liefert den ursprünglichen Fall zurück, statt einen zweiten zu erzeugen. Derselbe Schlüssel mit einem anderen Body ist ein 409. Wird 24 Stunden aufbewahrt. Dringend empfohlen: eine doppelte Netzanmeldung kostet einen Credit und muss von Hand zurückgezogen werden."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "text": {
                    "type": "string"
                  },
                  "declared": {
                    "type": "string",
                    "description": "Das deklarierte Objekt als JSON-Text."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Das unveränderliche Antwortpaket.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InformationResponse"
                }
              }
            }
          },
          "413": {
            "description": "Material zu groß.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "Nicht unterstützter Medientyp.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/anmeldungen/{id}/information-requests/{requestId}/responses/{responseId}/submit": {
      "post": {
        "operationId": "submitInformationResponse",
        "summary": "Eine vollständige Antwort einreichen",
        "description": "Sandbox: löst diese Anforderung und öffnet vor der Bestätigung eine deterministische Folgeanforderung, sodass Tests mehr als eine Runde durchlaufen. Live: verschiebt die Antwort nach `queued_for_operator`; sie behauptet nicht, dass ein VNB sie angenommen hat, weil die Live-Übermittlung an den VNB vom Fallteam betrieben wird. Eine spätere VNB-Rückfrage erzeugt eine neue Webhook-Anforderung.",
        "tags": [
          "Der Fall"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die Fall-ID, z. B. `anm_7Kq3ZmR8vT`. IDs tragen immer das Präfix `anm_`, was das statische Segment `14a` auf dem Erstellpfad gegenüber diesem Parameter eindeutig hält."
          },
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die `information_request.id` aus dem Webhook, auf der Umgebung dieses Schlüssels."
          },
          {
            "name": "responseId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Die von POST /responses zurückgegebene Antwort-ID auf der Umgebung dieses Schlüssels."
          }
        ],
        "responses": {
          "200": {
            "description": "Die Antwort nach Einreichung oder Einreihung in die Warteschlange.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InformationResponse"
                }
              }
            }
          },
          "422": {
            "description": "Die Antwort ist noch nicht zur Einreichung bereit.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer wnz_live_…` auf der Live-Umgebung, `Authorization: Bearer wnz_test_…` in der Sandbox. Das Präfix entscheidet über die Umgebung; sonst nichts. Beide werden in der Konsole unter https://www.wisnots.com/netzanmeldung/portal unter Entwickler erzeugt und tragen den Scope `netz`."
      }
    },
    "schemas": {
      "Localised": {
        "type": "object",
        "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
        "properties": {
          "de": {
            "type": "string"
          },
          "en": {
            "type": "string"
          }
        },
        "required": [
          "de",
          "en"
        ]
      },
      "Evidence": {
        "type": "object",
        "description": "Woraus ein ausgelesener Wert gelesen wurde. Das Zitat ist ein wörtlicher Ausschnitt des ausgelesenen Dokumenttexts, sodass ein Aufrufer seinem eigenen Nutzer den Satz hinter einem Wert zeigen kann statt einer Konfidenz, mit der niemand etwas anfangen kann.",
        "properties": {
          "material_id": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "quote": {
            "type": "string"
          },
          "page": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "material_id",
          "quote"
        ]
      },
      "Site": {
        "type": "object",
        "description": "Wo die Anlage physisch steht. Ein Postfach ist kein gültiger Standort.",
        "properties": {
          "street": {
            "type": "string",
            "x-nr": 1002
          },
          "house_number": {
            "type": "string",
            "x-nr": 1003
          },
          "postal_code": {
            "type": "string",
            "pattern": "^[0-9]{5}$",
            "x-nr": 1007
          },
          "city": {
            "type": "string",
            "x-nr": 1008
          },
          "address_addition": {
            "type": "string",
            "x-nr": 1004
          },
          "district": {
            "type": "string",
            "x-nr": 1009,
            "description": "Ortsteil."
          },
          "address_kind": {
            "type": "string",
            "enum": [
              "postal",
              "parcel"
            ],
            "x-nr": 1001,
            "description": "Ob der Standort als Postanschrift oder als Gemarkung und Flurstück angegeben ist. Wird aus dem Gesendeten abgeleitet, wenn es fehlt."
          }
        },
        "additionalProperties": false
      },
      "EndCustomer": {
        "type": "object",
        "description": "Der Anschlussnutzer: die Person oder Organisation, die das Gerät betreibt.",
        "properties": {
          "first_name": {
            "type": "string",
            "x-nr": 1102
          },
          "last_name": {
            "type": "string",
            "x-nr": 1101,
            "description": "Name oder Firmenname: der Name einer Firma steht hier, ohne Vornamen."
          },
          "email": {
            "type": "string",
            "format": "email",
            "x-nr": 1110
          },
          "phone": {
            "type": "string",
            "x-nr": 1109
          },
          "is_owner": {
            "type": "boolean",
            "description": "True, wenn ihnen die Immobilie gehört. False heißt, eine Zustimmung des Vermieters kann erforderlich sein."
          },
          "street": {
            "type": "string",
            "x-nr": 1103,
            "description": "Die eigene Anschrift des Anschlussnutzers, Felder 1103 bis 1108. Verschieden von `site`: ein Vermieter, der für das Dach eines Mieters einreicht, wohnt woanders. Blockiert nie; die Standortadresse tut es."
          },
          "house_number": {
            "type": "string",
            "x-nr": 1104
          },
          "address_addition": {
            "type": "string",
            "x-nr": 1105
          },
          "postal_code": {
            "type": "string",
            "x-nr": 1106
          },
          "city": {
            "type": "string",
            "x-nr": 1107
          },
          "district": {
            "type": "string",
            "x-nr": 1108
          },
          "iban": {
            "type": "string",
            "x-nr": 1111,
            "description": "Nur PV. Wohin die Einspeisevergütung gezahlt wird."
          },
          "company_status": {
            "type": "string",
            "enum": [
              "in_difficulty",
              "open_recovery_claims",
              "neither"
            ],
            "x-nr": 1112,
            "description": "Nur PV. Die EU-Beihilfeerklärung, die eine Firma abgibt, bevor sie EEG-Vergütung erhalten kann."
          }
        },
        "additionalProperties": false
      },
      "ContractHolder": {
        "type": "object",
        "description": "Anschlussnehmer, Felder 1201 bis 1210: die Partei, die den Anschlussvertrag hält, wenn das nicht der Endkunde ist, der die Anlage nutzt. Typischerweise ein Vermieter. Lassen Sie den Block weg, wenn es dieselbe Person ist.",
        "properties": {
          "last_name": {
            "type": "string",
            "x-nr": 1201,
            "description": "Name oder Firmenname."
          },
          "first_name": {
            "type": "string",
            "x-nr": 1202
          },
          "street": {
            "type": "string",
            "x-nr": 1203
          },
          "house_number": {
            "type": "string",
            "x-nr": 1204
          },
          "postal_code": {
            "type": "string",
            "x-nr": 1206
          },
          "city": {
            "type": "string",
            "x-nr": 1207
          },
          "phone": {
            "type": "string",
            "x-nr": 1209
          },
          "email": {
            "type": "string",
            "format": "email",
            "x-nr": 1210
          }
        },
        "additionalProperties": false
      },
      "Billing": {
        "type": "object",
        "description": "Rechnungsempfänger, Felder 1301 bis 1310: wer die Rechnung erhält, wenn das nicht der Endkunde ist. Lassen Sie den Block weg, wenn er es ist.",
        "properties": {
          "name": {
            "type": "string",
            "x-nr": 1301,
            "description": "Name oder Firmenname."
          },
          "first_name": {
            "type": "string",
            "x-nr": 1302
          },
          "street": {
            "type": "string",
            "x-nr": 1303
          },
          "house_number": {
            "type": "string",
            "x-nr": 1304
          },
          "address_addition": {
            "type": "string",
            "x-nr": 1305
          },
          "postal_code": {
            "type": "string",
            "x-nr": 1306
          },
          "city": {
            "type": "string",
            "x-nr": 1307
          },
          "district": {
            "type": "string",
            "x-nr": 1308
          },
          "phone": {
            "type": "string",
            "x-nr": 1309
          },
          "email": {
            "type": "string",
            "format": "email",
            "x-nr": 1310
          }
        },
        "additionalProperties": false
      },
      "Installer": {
        "type": "object",
        "description": "Der Elektrofachbetrieb. Auf der Einreichung genannt, ob er der API-Aufrufer ist oder nicht, weil der Prozess des Netzbetreibers ihn erwartet.",
        "properties": {
          "company": {
            "type": "string",
            "x-nr": 1401
          },
          "street": {
            "type": "string",
            "x-nr": 1402
          },
          "house_number": {
            "type": "string",
            "x-nr": 1403
          },
          "address_addition": {
            "type": "string",
            "x-nr": 1404
          },
          "postal_code": {
            "type": "string",
            "x-nr": 1405
          },
          "city": {
            "type": "string",
            "x-nr": 1406
          },
          "district": {
            "type": "string",
            "x-nr": 1407
          },
          "phone": {
            "type": "string",
            "x-nr": 1408
          },
          "email": {
            "type": "string",
            "format": "email",
            "x-nr": 1409
          },
          "registration_operator": {
            "type": "string",
            "x-nr": 1410,
            "description": "Der Netzbetreiber, bei dem die Eintragungsnummer vergeben wurde. Eine Eintragung im Installateurverzeichnis gilt je Netzbetreiber, die Nummer allein identifiziert sie also nicht."
          },
          "registry_number": {
            "type": "string",
            "x-nr": 1411,
            "description": "Eintragsnummer im Installateurverzeichnis des Netzbetreibers."
          },
          "contact_last_name": {
            "type": "string",
            "x-nr": 1501,
            "description": "Die Person, die zu diesem Fall anzurufen ist."
          },
          "contact_first_name": {
            "type": "string",
            "x-nr": 1502
          },
          "contact_phone": {
            "type": "string",
            "x-nr": 1503
          },
          "contact_email": {
            "type": "string",
            "format": "email",
            "x-nr": 1504
          },
          "salutation": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "care_of": {
            "type": "string"
          },
          "register_court_number": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "fax": {
            "type": "string"
          },
          "directory_reference": {
            "type": "string",
            "description": "Die Eintragung der Firma im Installateurverzeichnis des Netzbetreibers, wenn das Portal sie aus dieser Liste auswählt und nicht über die Nummer."
          }
        },
        "additionalProperties": false
      },
      "Applicant": {
        "type": "object",
        "description": "Die antragstellende Firma, wenn der Aufrufer im Namen seines Kunden anmeldet. Optional; ohne diese Angaben wird eine Anmeldung im Namen des Kunden gestellt. Der nationale Datensatz kennt diese Partei nicht, deshalb tragen die Felder keine Nummer.",
        "properties": {
          "salutation": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "care_of": {
            "type": "string"
          },
          "company": {
            "type": "string"
          },
          "register_court_number": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "house_number": {
            "type": "string"
          },
          "address_addition": {
            "type": "string"
          },
          "postal_code": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "district": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string"
          },
          "fax": {
            "type": "string"
          },
          "dob": {
            "type": "string",
            "format": "date"
          },
          "mobile": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "Builder": {
        "type": "object",
        "description": "Anlagenerrichter, Felder 1601 bis 1609: die Firma, die die Anlage errichtet hat, wenn das nicht der Elektrofachbetrieb in `installer` ist. Auf einem PV-Dach sind das oft zwei Firmen, und der Standard fragt nach beiden.",
        "properties": {
          "company": {
            "type": "string",
            "x-nr": 1601
          },
          "street": {
            "type": "string",
            "x-nr": 1602
          },
          "house_number": {
            "type": "string",
            "x-nr": 1603
          },
          "address_addition": {
            "type": "string",
            "x-nr": 1604
          },
          "postal_code": {
            "type": "string",
            "x-nr": 1605
          },
          "city": {
            "type": "string",
            "x-nr": 1606
          },
          "district": {
            "type": "string",
            "x-nr": 1607
          },
          "phone": {
            "type": "string",
            "x-nr": 1608
          },
          "email": {
            "type": "string",
            "format": "email",
            "x-nr": 1609
          }
        },
        "additionalProperties": false
      },
      "FeedIn": {
        "type": "object",
        "description": "Wie die Einspeisung veräußert und vergütet wird. Nur PV und Speicher; ein § 14a-Fall, der es sendet, bekommt einen 422, der das Feld benennt, denn der Standard fragt einen Ladepunkt nicht, wie seine Einspeisung vergütet wird.",
        "properties": {
          "sale_form": {
            "type": "string",
            "enum": [
              "free_of_charge",
              "subsidised_direct_marketing",
              "feed_in_tariff",
              "tenant_electricity",
              "other_direct_marketing",
              "unsubsidised"
            ],
            "x-nr": 2018,
            "description": "Gewünschte Veräußerungsform der Einspeisung."
          },
          "remuneration_notes": {
            "type": "string",
            "x-nr": 2019
          },
          "remote_control_proof": {
            "type": "string",
            "x-nr": 2020,
            "description": "Nur PV. Nachweis, dass die Anlage für die Direktvermarktung fernsteuerbar ist."
          },
          "grid_operator_confirmation": {
            "type": "boolean",
            "x-nr": 2021,
            "description": "Nur PV."
          },
          "eeg_commissioning_at": {
            "type": "string",
            "format": "date",
            "x-nr": 2005,
            "description": "EEG-Inbetriebnahmedatum. Kann `connection.planned_commissioning_at` gleichen; der Standard hält sie auseinander, weil das EEG-Datum den Tarif festlegt."
          }
        },
        "additionalProperties": false
      },
      "Device": {
        "type": "object",
        "description": "Die Anlage, die angemeldet wird. Ein Objekt für alle fünf Anlagentypen: ein Feld, das nur ein Typ fragt, ist so markiert, und es für einen anderen Typ zu senden ist ein 422, der das Feld benennt.",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "charge_point",
              "heat_pump",
              "pv",
              "battery",
              "air_conditioning"
            ],
            "description": "Nur auf `POST /v1/anmeldungen/14a`, das keinen Typ festlegt. Die typisierten Routen haben es bereits beantwortet; ein anderer Wert dort ist ein 422."
          },
          "status": {
            "type": "string",
            "enum": [
              "planned",
              "installed",
              "in_operation"
            ],
            "description": "Bestimmt, welche Einreichungen abgeleitet werden. Eine Anlage, die bereits in Betrieb ist, braucht keinen Netzanschlussantrag, nur die Anmeldung."
          },
          "installed_at": {
            "type": "string",
            "format": "date",
            "description": "Inbetriebnahmedatum. Ein Datum vor 2024-01-01 macht dies zu einer Bestandsanlage, bei der die Teilnahme an § 14a eine freiwillige Entscheidung statt einer Pflicht ist. Betrieblich, kein Feld des Standards: das EEG-Inbetriebnahmedatum (Feld 2005) ist `feed_in.eeg_commissioning_at` und existiert nur für PV und Speicher."
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "description": "Baugleiche Einheiten an diesem Anschluss. Felder 5001, 6001, 4005 und 7001; PV zählt stattdessen Wechselrichter, siehe `inverter_count`."
          },
          "manufacturer": {
            "type": "string",
            "description": "Felder 5002, 6002, 4001 und 7002. PV: pro Wechselrichter, siehe `inverters`."
          },
          "model": {
            "type": "string",
            "description": "Felder 5003, 6003, 4002 und 7003."
          },
          "grid_draw_kw": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100,
            "description": "Maximale Netzbezugsleistung. Ladepunkt 5017, Speicher 4018, Klimaanlage 7014."
          },
          "separate_meter": {
            "type": "boolean",
            "description": "Messung über separaten Zähler. Felder 5011, 6010, 4012 und 7008."
          },
          "meter_exists": {
            "type": "boolean",
            "description": "Zähler bereits vorhanden. Ladepunkt 5012, Speicher 4013, Klimaanlage 7009."
          },
          "existing_installation": {
            "type": "string",
            "enum": [
              "Ja (Anlage ohne Steuerung § 14a alte Fassung)",
              "Ja (Anlage mit Steuerung § 14a alte Fassung)",
              "Nein"
            ],
            "description": "Bestandsanlage vor dem 01.01.2024, im Wortlaut der Netzbetreiber. Felder 5014, 6013, 4015 und 7011."
          },
          "control_mode": {
            "type": "string",
            "enum": [
              "Direkt",
              "Energiemanagementsystem"
            ],
            "description": "Steuerungsart. Felder 5015, 6014, 4016 und 7012."
          },
          "tariff_module": {
            "type": "string",
            "enum": [
              "Modul 1",
              "Modul 2"
            ],
            "description": "Netzentgeltmodul. Felder 5016, 6015, 4017 und 7013."
          },
          "load_management": {
            "type": "string",
            "enum": [
              "Nein",
              "Ja (dynamisch)",
              "Ja (statisch)"
            ],
            "description": "Energie- bzw. Lastmanagement. Ladepunkt 5019, Speicher 4020, Klimaanlage 7016."
          },
          "control_interface": {
            "type": "string",
            "enum": [
              "Relais",
              "Digital (EEBUS)"
            ],
            "description": "Speicher 4023, Klimaanlage 7017."
          },
          "form_factor": {
            "type": "string",
            "enum": [
              "Ladesäule",
              "Ladebox",
              "Sonstiges"
            ],
            "x-nr": 5006,
            "description": "Nur Ladepunkt."
          },
          "heat_pump_type": {
            "type": "string",
            "enum": [
              "air_water",
              "brine_water",
              "water_water",
              "other"
            ],
            "x-nr": 6004,
            "description": "Nur Wärmepumpe."
          },
          "starting_current_a": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Maximaler Anlaufstrom in A. Wärmepumpe 6007, Klimaanlage 7005."
          },
          "electrical_power_kw": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 7004,
            "description": "Nur Klimaanlage."
          },
          "inverter_count": {
            "type": "integer",
            "minimum": 1,
            "x-nr": 3008,
            "description": "Nur PV: baugleiche Wechselrichter. Die Werte jedes einzelnen stehen in `inverters`."
          },
          "apparent_power_kva": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Summe der maximalen Scheinleistungen. PV 3009, Speicher 4006."
          },
          "active_power_kw": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Summe der maximalen Wirkleistungen. PV 3010, Speicher 4019 (Einspeisung)."
          },
          "module_power_kwp": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 3101,
            "description": "Nur PV: gesamte Modulleistung."
          },
          "operating_mode": {
            "type": "string",
            "description": "Betriebsweise, im Wortlaut der Netzbetreiber. PV (3001): `Überschusseinspeisung`, `Volleinspeisung`, `Nulleinspeisung`. Speicher (4008): `Kein Bezug und keine Einspeisung`, `Bezug und keine Einspeisung`, `Kein Bezug aber Einspeisung`, `Bezug und Einspeisung`."
          },
          "coupling": {
            "type": "string",
            "enum": [
              "Eigener Wechselrichter (AC-Kopplung)",
              "Gemeinsamer Wechselrichter mit Erzeugungsanlage (DC-Kopplung)"
            ],
            "x-nr": 4007,
            "description": "Nur Speicher."
          },
          "storage_capacity_kwh": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 4021,
            "description": "Nur Speicher."
          },
          "zerez_id": {
            "type": "string",
            "x-nr": 4022,
            "description": "Nur Speicher. PV-Wechselrichter tragen ihre in `inverters`."
          }
        },
        "additionalProperties": false
      },
      "Inverter": {
        "type": "object",
        "description": "Ein Wechselrichter einer PV-Anlage. Der Standard fragt diese einmal pro Einheit, sie reisen also als `inverters`-Array und kommen als `inverters.0.manufacturer` und so weiter zurück.",
        "properties": {
          "manufacturer": {
            "type": "string",
            "x-nr": 3003
          },
          "model": {
            "type": "string",
            "x-nr": 3004
          },
          "apparent_power_kva": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 3006
          },
          "active_power_kw": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 3007
          },
          "zerez_id": {
            "type": "string",
            "x-nr": 3015
          }
        },
        "additionalProperties": false
      },
      "Protection": {
        "type": "object",
        "description": "NA-Schutz, Felder 3301 bis 3305. Nur PV und Speicher.",
        "properties": {
          "design": {
            "type": "string",
            "enum": [
              "Zentral am Zählerplatz",
              "Integriert",
              "Zentral am Zählerplatz und integriert in Kombination"
            ],
            "x-nr": 3301
          },
          "manufacturer": {
            "type": "string",
            "x-nr": 3302
          },
          "model": {
            "type": "string",
            "x-nr": 3303
          },
          "certificate": {
            "type": "string",
            "x-nr": 3304
          },
          "zerez_id": {
            "type": "string",
            "x-nr": 3305
          }
        },
        "additionalProperties": false
      },
      "Connection": {
        "type": "object",
        "description": "Der Netzanschluss, an dem das Gerät hängt.",
        "properties": {
          "meter_number": {
            "type": "string",
            "description": "Zählernummer. Ein Name, die Nummer folgt dem Typ: 5013, 6012, 3014, 4014 oder 7010."
          },
          "location_in_plan": {
            "type": "string",
            "x-nr": 2003,
            "description": "Aufstellungsort im Lageplan."
          },
          "grid_draw_kw": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 2008,
            "description": "Netzwirksamer Leistungsbezug am Netzanschluss, die Summe des Anschlusses. Nicht die des Geräts selbst, das ist `device.grid_draw_kw`."
          },
          "metering_operator": {
            "type": "string",
            "x-nr": 2011,
            "description": "Ladepunkt und Wärmepumpe."
          },
          "has_smart_meter": {
            "type": "boolean",
            "description": "Ein intelligentes Messsystem ist installiert. Das ist das Hauptsignal dafür, wer die Steuerung ausführen kann; es anzugeben beantwortet also oft `section_14a.control_responsibility` für Sie."
          },
          "planned_commissioning_at": {
            "type": "string",
            "format": "date",
            "x-nr": 2004,
            "description": "Geplanter Inbetriebsetzungstermin, der Tag, an dem die Anlage in Betrieb gehen soll. Optional: fehlt er, wird der Fall beim Netzbetreiber mit dem Tag 14 Tage nach der Einreichung bei uns angemeldet, nach Berliner Kalender, oder, liegt dieser Tag bei der Vorbereitung der Anmeldung schon zurück, mit dem Tag 14 Tage danach; wer einen Fall ohne ihn einreicht, stimmt dem zu."
          },
          "single_line_diagram": {
            "type": "string",
            "description": "Einpolige Darstellung des Messkonzepts. Feld 2006 auf einem PV-Fall, 2007 auf den vier § 14a-Familien; ein Name, die Nummer folgt der Familie. Ein Verweis auf die Zeichnung, nicht die Zeichnung: die senden Sie als `material`."
          },
          "tab_confirmed": {
            "type": "boolean",
            "x-nr": 2009,
            "description": "Der Installateur bestätigt, dass die TAB des Netzbetreibers für steuerbare Verbrauchseinrichtungen erfüllt sind."
          },
          "fuse_change": {
            "type": "string",
            "enum": [
              "sufficient",
              "increase_required"
            ],
            "x-nr": 2022
          },
          "fuse_required_a": {
            "type": "number",
            "exclusiveMinimum": 0,
            "x-nr": 2023,
            "description": "In Ampere."
          },
          "grey_power": {
            "type": "string",
            "enum": [
              "renewable_only",
              "grey_power"
            ],
            "x-nr": 2013,
            "description": "Nur Ladepunkt und Speicher."
          },
          "grey_power_balancing_group": {
            "type": "string",
            "x-nr": 2014
          },
          "grey_power_marketer": {
            "type": "string",
            "x-nr": 2015
          },
          "existing_controllable_device": {
            "type": "string",
            "enum": [
              "none",
              "charge_point",
              "heat_pump",
              "air_conditioning",
              "battery"
            ],
            "x-nr": 2001,
            "description": "Nur PV. Ein § 14a-Gerät, das bereits am Anschluss hängt."
          },
          "control_equipment": {
            "type": "string",
            "enum": [
              "new_default_mso",
              "new_competitive_mso",
              "existing"
            ],
            "x-nr": 2002,
            "description": "Nur PV. Die Steuerungseinrichtung nach § 9 EEG."
          },
          "metering_operator_choice": {
            "type": "string",
            "enum": [
              "default_mso",
              "competitive_mso"
            ],
            "x-nr": 2012,
            "description": "Nur PV."
          },
          "time_series_type_draw": {
            "type": "string",
            "enum": [
              "sls",
              "lgs",
              "other"
            ],
            "x-nr": 2016,
            "description": "Nur Speicher."
          },
          "time_series_type_feed_in": {
            "type": "string",
            "enum": [
              "sol",
              "egs",
              "other"
            ],
            "x-nr": 2017,
            "description": "Nur Speicher."
          }
        },
        "additionalProperties": false
      },
      "Consent": {
        "type": "object",
        "description": "Das Einverständnis des Betreibers, nach § 14a angemeldet zu werden. Die Einreichung des Falls ist dieses Einverständnis: die Übergabe ist die Anweisung zur Einreichung, deshalb ist hier nichts Pflicht und kein Fall wartet darauf. Senden Sie es, wenn der Betreiber vor der Einreichung zugestimmt hat; es wird als Nachweis über Zeitpunkt und Weg festgehalten.",
        "properties": {
          "given_by": {
            "type": "string",
            "enum": [
              "end_customer",
              "installer_on_behalf"
            ]
          },
          "given_at": {
            "type": "string",
            "format": "date-time"
          },
          "channel": {
            "type": "string",
            "description": "Auf welchem Weg die Zustimmung erteilt wurde, in Ihren eigenen Worten: `phone`, `email`, `written` oder der Name Ihrer Plattform."
          },
          "reference": {
            "type": "string",
            "description": "Ihre eigene Kennung für den Nachweis des Einverständnisses: eine Anruf-ID, eine Mail-ID, eine Dokument-ID."
          }
        },
        "additionalProperties": false
      },
      "Section14a": {
        "type": "object",
        "description": "Die Besonderheiten nach § 14a.",
        "properties": {
          "modules": {
            "type": "string",
            "example": "1",
            "description": "Welches Netzentgeltmodul gewählt ist, als Text: `1` ist die pauschale Reduzierung und der Regelfall, `2` die prozentuale Reduzierung des Arbeitspreises und schließt 1 aus, `3` das zeitvariable Netzentgelt und kombiniert mit 1. Gesendet als `1` oder im Wortlaut des Netzbetreibers, `Modul 1`."
          },
          "control_responsibility": {
            "type": "string",
            "enum": [
              "grid_operator",
              "metering_operator"
            ],
            "x-nr": 2010,
            "description": "Wer die netzorientierte Steuerung ausführt: der Netzbetreiber mit eigener Steuerungseinrichtung, oder der Messstellenbetreiber über das Smart-Meter-Gateway."
          },
          "is_controllable": {
            "type": "boolean",
            "description": "Ob das Gerät überhaupt eine steuerbare Verbrauchseinrichtung ist. Ladepunkt 5010, Speicher 4011, Klimaanlage 7007; eine Wärmepumpe ist es immer."
          },
          "critical_infrastructure": {
            "type": "boolean",
            "x-nr": 7006,
            "description": "Nur Klimaanlage: dient betriebsnotwendigen Zwecken oder kritischer Infrastruktur, was sie von der Steuerung ausnimmt."
          },
          "consent": {
            "$ref": "#/components/schemas/Consent"
          }
        },
        "additionalProperties": false
      },
      "Declared": {
        "type": "object",
        "description": "Alles, was der Aufrufer bereits weiß. Jedes Feld ist optional: ein Aufrufer, der nichts weiß, sendet gar kein `declared` und verlässt sich auf `material`. Deklarierte Werte sind maßgeblich und werden von der Erkennung nie überschrieben.",
        "properties": {
          "site": {
            "$ref": "#/components/schemas/Site"
          },
          "end_customer": {
            "$ref": "#/components/schemas/EndCustomer"
          },
          "contract_holder": {
            "$ref": "#/components/schemas/ContractHolder"
          },
          "billing": {
            "$ref": "#/components/schemas/Billing"
          },
          "installer": {
            "$ref": "#/components/schemas/Installer"
          },
          "applicant": {
            "$ref": "#/components/schemas/Applicant"
          },
          "builder": {
            "$ref": "#/components/schemas/Builder"
          },
          "device": {
            "$ref": "#/components/schemas/Device"
          },
          "inverters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Inverter"
            },
            "description": "Nur PV: ein Eintrag pro Wechselrichter, in der Reihenfolge, in der Sie sie zählen."
          },
          "connection": {
            "$ref": "#/components/schemas/Connection"
          },
          "protection": {
            "$ref": "#/components/schemas/Protection"
          },
          "feed_in": {
            "$ref": "#/components/schemas/FeedIn"
          },
          "section_14a": {
            "$ref": "#/components/schemas/Section14a"
          },
          "nr": {
            "type": "object",
            "description": "Hintertür zu jedem Feld des nationalen Datensatzes über seine Nummer, auch zu Feldern, denen diese API noch keinen Namen gegeben hat. Schlüssel sind Feldnummern als Zeichenketten. Ein benanntes Feld und sein nummerierter Zwilling dürfen nicht beide mit verschiedenen Werten gesendet werden; das ist ein 422.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            },
            "propertyNames": {
              "pattern": "^[0-9]{4}$"
            },
            "example": {
              "1004": "Hinterhaus"
            }
          }
        },
        "additionalProperties": false
      },
      "Material": {
        "oneOf": [
          {
            "type": "object",
            "title": "Fixture (sandbox only)",
            "description": "Ein benanntes Dokument, dessen Leseergebnis wir bereits kennen. Kostet nichts, macht keinen Modellaufruf und ist jedes Mal identisch, sodass ein Bildschirm im Bau hundertmal am Tag dagegen neu gebaut werden kann.\n\nEs nimmt trotzdem den echten Weg: die Werte landen auf dem Fall genau so, wie ein von uns gelesenes Dokument sie dort ablegen würde, samt Belegen, sodass sich nachgelagert nichts anders verhält. Ein echter Upload in der Sandbox läuft weiterhin durch die echte Erkennung, denn ein Fixture kann Ihnen nicht zeigen, wie sich IHRE Dokumente verhalten.\n\nUnter einem Live-Schlüssel abgelehnt: ein aus einem Fixture gebauter Live-Fall wäre eine Einreichung aus erfundenen Tatsachen über einen echten Kunden.",
            "properties": {
              "fixture": {
                "type": "string",
                "enum": [
                  "wallbox-invoice-de",
                  "heat-pump-invoice-de",
                  "pv-invoice-de",
                  "battery-invoice-de",
                  "air-conditioning-invoice-de"
                ],
                "description": "Eines pro Anlagentyp. Jedes ist die Rechnung eines Installateurs für eine Anlage, mit Standort und Briefkopf an verschiedenen Adressen, damit ein Bildschirm, der sie verwechselt, es zeigt."
              }
            },
            "required": [
              "fixture"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "Text",
            "properties": {
              "kind": {
                "type": "string",
                "const": "text"
              },
              "content": {
                "type": "string",
                "maxLength": 100000
              },
              "label": {
                "type": "string",
                "description": "Was das ist, für den Prüfpfad. Wird nie als Daten geparst."
              }
            },
            "required": [
              "kind",
              "content"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "Inline file",
            "description": "Für kleine Dateien, damit der häufige Fall ein Aufruf ist. Über der Inline-Grenze registrieren Sie das Dokument am Fall und laden zu dem Ziel hoch, das er zurückgibt.",
            "properties": {
              "kind": {
                "type": "string",
                "const": "file"
              },
              "filename": {
                "type": "string"
              },
              "content_type": {
                "type": "string",
                "enum": [
                  "application/pdf",
                  "image/jpeg",
                  "image/png",
                  "image/webp",
                  "image/heic",
                  "text/plain"
                ]
              },
              "data": {
                "type": "string",
                "contentEncoding": "base64"
              }
            },
            "required": [
              "kind",
              "filename",
              "content_type",
              "data"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "title": "Already-uploaded file",
            "description": "Bytes, die bereits über den Zwei-Aufruf-Weg hochgeladen wurden. Der Aufrufer sendet die Speicher-ID zurück, die das Upload-Ziel geliefert hat.",
            "properties": {
              "kind": {
                "type": "string",
                "const": "file"
              },
              "storage_id": {
                "type": "string"
              },
              "filename": {
                "type": "string"
              },
              "content_type": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "storage_id",
              "filename",
              "content_type"
            ],
            "additionalProperties": false
          }
        ],
        "description": "Ein Stück rohes Material. Es gibt bewusst keine `url`-Variante: eine vom Aufrufer gelieferte URL serverseitig abzurufen ist eine Angriffsfläche für Request-Forgery, deshalb werden Bytes entweder inline gesendet oder zu einem Ziel hochgeladen, das wir ausgeben.\n\nEin PDF, das für ein einzelnes Lesen zu groß ist, wird in Abschnitte aufeinanderfolgender Seiten geschnitten und Abschnitt für Abschnitt gelesen, bis zu 60 Seiten; die Texte werden in Seitenreihenfolge zusammengefügt, sodass ein Zitat in `evidence` auf seiner Seite zu finden ist. Ein zu großes Foto wird verkleinert. Nur eine einzelne Seite, die allein die Grenze der Erkennung überschreitet, ist unlesbar, und das Material sagt es."
      },
      "ResolvedField": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Punktpfad in `declared`, z. B. `site.street`."
          },
          "nr": {
            "type": "integer",
            "description": "Die Nummer des Felds im nationalen Datensatz."
          },
          "label": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "value": {},
          "unit": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "declared",
              "extracted",
              "derived"
            ],
            "description": "Woher der aufgelöste Wert stammt. `declared` wurde vom Aufrufer geliefert und wird nie überschrieben. `extracted` wurde aus Material gelesen. `derived` haben wir aus anderen Werten berechnet. Vorrang bei Widerspruch: declared > extracted > derived, und die unterlegenen Werte werden in `conflicts` gemeldet statt verworfen."
          },
          "confidence": {
            "type": "string",
            "enum": [
              "certain",
              "probable"
            ],
            "description": "Nur für `extracted` von Bedeutung. `probable` heißt, der Wert wurde gefolgert statt angegeben, zum Beispiel ein Inbetriebnahmedatum aus einem Lieferdatum."
          },
          "evidence": {
            "$ref": "#/components/schemas/Evidence"
          }
        },
        "required": [
          "path",
          "label",
          "value",
          "source"
        ]
      },
      "OpenField": {
        "type": "object",
        "description": "Ein Feld, das wir noch brauchen. Bezeichnungen, Hilfetext und Optionen sind der Wortlaut des Datensatzes, die Worte der Netzbetreiber, sodass ein Aufrufer seinem Nutzer die Frage in denselben Worten stellen kann, die der Netzbetreiber später verwendet.",
        "properties": {
          "path": {
            "type": "string"
          },
          "nr": {
            "type": "integer"
          },
          "label": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "help": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "hint": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "string",
              "number",
              "boolean",
              "date",
              "enum"
            ]
          },
          "unit": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "label": {
                  "type": "object",
                  "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
                  "properties": {
                    "de": {
                      "type": "string"
                    },
                    "en": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "de",
                    "en"
                  ]
                }
              }
            }
          },
          "operator_specific": {
            "type": "boolean",
            "description": "Vorhanden und true, wenn dieser Eintrag den Netzbetreiber des Falls widerspiegelt statt den nationalen Datensatz, sodass Sie erkennen, was sich nicht auf den nächsten Netzbetreiber übertragen lässt. Erscheint nur unter einem netzbetreiberbezogenen Schlüssel; siehe `requirements` auf dem Fall.\n\nLesen Sie es zusammen mit `nr`: keine Nummer heißt eine Frage, die der Standard gar nicht stellt, und ihr Pfad liegt unter `operator.`; eine Nummer heißt die eigene Frage des Standards mit den Antwortoptionen dieses Netzbetreibers, am Pfad des Standards, sodass nichts, was Sie bereits senden, umbenannt werden muss."
          },
          "owed_by": {
            "type": "string",
            "enum": [
              "end_customer",
              "installer",
              "us",
              "operator"
            ],
            "description": "Von wem die Antwort erwartet wird. Das lässt einen Aufrufer die Frage weiterleiten statt raten, und deshalb ist eine Liste offener Felder nicht einfach eine Liste von Validierungsfehlern."
          },
          "blocking": {
            "type": "boolean",
            "description": "True, wenn die Einreichung ohne es nicht erfolgen kann. Nicht blockierende offene Felder werden akzeptiert: eine teilweise Erfassung ist gewollt, und den Rest fragen wir danach ab."
          }
        },
        "required": [
          "path",
          "label",
          "type",
          "owed_by",
          "blocking"
        ]
      },
      "Conflict": {
        "type": "object",
        "description": "Zwei Quellen haben für dasselbe Feld verschiedene Werte geliefert. Wir wählen nicht still einen aus: eine Falschangabe gegenüber einem Netzbetreiber ist schlimmer als eine Rückfrage. Den Wert zu deklarieren entscheidet es, denn declared gewinnt.",
        "properties": {
          "path": {
            "type": "string"
          },
          "nr": {
            "type": "integer"
          },
          "label": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "blocking": {
            "type": "boolean"
          },
          "message": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "candidates": {
            "type": "array",
            "minItems": 2,
            "items": {
              "type": "object",
              "properties": {
                "value": {},
                "source": {
                  "type": "string",
                  "enum": [
                    "declared",
                    "extracted",
                    "derived"
                  ],
                  "description": "Woher der aufgelöste Wert stammt. `declared` wurde vom Aufrufer geliefert und wird nie überschrieben. `extracted` wurde aus Material gelesen. `derived` haben wir aus anderen Werten berechnet. Vorrang bei Widerspruch: declared > extracted > derived, und die unterlegenen Werte werden in `conflicts` gemeldet statt verworfen."
                },
                "evidence": {
                  "$ref": "#/components/schemas/Evidence"
                }
              }
            }
          }
        },
        "required": [
          "path",
          "label",
          "candidates",
          "blocking"
        ]
      },
      "Notice": {
        "type": "object",
        "description": "Etwas Wissenswertes, das nichts blockiert.",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "existing_operator_approval_found",
              "existing_installation",
              "multiple_installations_detected",
              "material_unreadable",
              "device_not_controllable",
              "module_choice_defaulted"
            ]
          },
          "message": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "detail": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "required": [
          "code",
          "message"
        ]
      },
      "MaterialSummary": {
        "type": "object",
        "description": "Ein Stück Material und wie weit sein Lesen gekommen ist. Ein `file` trägt `filename`, `content_type` und `bytes`; ein `text` sein `label`; ein `fixture` seine ID. `status` durchläuft `pending` (in der Warteschlange), `reading` (die Erkennung hat es), dann `read` oder `unreadable`. Fertig heißt einer der letzten beiden, und der `status` des Falls selbst ist `extracting`, solange irgendein Eintrag in den ersten beiden steckt: fragen Sie den `status` des Falls ab, nicht den ersten Eintrag, der `read` zeigt.",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "file",
              "fixture"
            ]
          },
          "filename": {
            "type": "string",
            "description": "Der Name einer Datei, wie gesendet."
          },
          "label": {
            "type": "string",
            "description": "Das Label eines Texts, wie gesendet; der Titel eines Fixtures."
          },
          "content_type": {
            "type": "string",
            "description": "Der Medientyp einer Datei, wie die Bytes gelesen wurden."
          },
          "bytes": {
            "type": "integer",
            "description": "Die Größe einer Datei."
          },
          "fixture": {
            "type": "string",
            "description": "Die ID des Fixtures, auf einem Fixture."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "reading",
              "read",
              "unreadable",
              "rejected"
            ],
            "description": "`pending` und `reading` heißen beide warten. `read`: Text entnommen und Werte auf den Fall aufgelöst. `unreadable`: die Erkennung konnte es nicht lesen, und der Fall sagt es. `rejected` ist reserviert: heute wird eine Datei, die die Erkennung nicht annehmen kann, beim Upload mit einem 415 abgelehnt und wird nie Material."
          },
          "extracted_chars": {
            "type": "integer",
            "description": "Wie viel Text die Erkennung entnommen hat, sobald `read`."
          },
          "reject_reason": {
            "type": "string",
            "enum": [
              "too_small",
              "too_large",
              "wrong_format",
              "unreadable"
            ],
            "description": "Reserviert mit `rejected`; wird heute nicht erzeugt."
          }
        },
        "required": [
          "id",
          "kind",
          "status"
        ]
      },
      "Filing": {
        "type": "object",
        "description": "Was wir im Namen des Aufrufers einreichen. Immer ein Antwortfeld, nie ein Anfragefeld: der Aufrufer sagt, was er installiert hat, wir entscheiden, was eingereicht werden muss.",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "section_14a",
              "commissioning_notice",
              "grid_connection_request",
              "meter_application",
              "change_notification"
            ]
          },
          "stage": {
            "type": "string"
          },
          "stage_label": {
            "type": "object",
            "description": "Eine kurze Zeichenkette in beiden Sprachen. Nur zur Anzeige; nie ein Vertragswert.",
            "properties": {
              "de": {
                "type": "string"
              },
              "en": {
                "type": "string"
              }
            },
            "required": [
              "de",
              "en"
            ]
          },
          "operator_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "kind",
          "stage"
        ]
      },
      "Case": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "external_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ihre Kennung, überall zurückgegeben."
          },
          "installer_ref": {
            "type": [
              "string",
              "null"
            ],
            "description": "Der Installationsbetrieb, für den der Fall angemeldet wird, so wie Sie ihn benannt haben. Null, wenn Ihr Konto selbst der Betrieb ist."
          },
          "operator_login": {
            "type": [
              "object",
              "null"
            ],
            "description": "Der Zugang des Betriebs beim Netzbetreiber für diesen Fall, und der Link des Betriebs. Null, bis der Fall einen Link hat: einen, der seinen Betrieb nennt (`installer_ref`), oder einen, den wir für ihn ausgestellt haben.\n\nJeder Betrieb hat bei jedem Netzbetreiber seinen eigenen Zugang, verwendet für seine Fälle dort und für nichts sonst. `link` ist die eigene Seite des Betriebs, um Netzbetreiber, Login und Passwort verschlüsselt zu übermitteln, so viele er hat: derselbe Link für alle seine Fälle, also einmal senden, und erneut, wenn der Status sagt, dass der Zugang fehlt oder abgelehnt wurde. Keine Registrierung und keine Einwilligung: die Einreichung des Falls ist die Einwilligung.\n\nEin übermittelter Zugang ersetzt nie einen früheren: er wird als neue Fassung aufbewahrt und ab dann verwendet. Wir können einen Link widerrufen; der Fall trägt dann seinen Nachfolger, und Zugänge, die über den widerrufenen kamen, werden nicht mehr verwendet.\n\nDer Link steht auch bei Live-Fällen in der Antwort, anders als der Unterschriftslink. Er kann einen Zugang nur hinzufügen, nie lesen oder ersetzen, und ihn zum Betrieb zu bringen ist der Zweck.\n\n`operator` und `status` sind null, solange der Netzbetreiber des Falls nicht feststeht; der Betrieb nennt ihn auf der Seite. Status: `missing` (noch keiner), `held`, `unusable` (das Portal des Netzbetreibers hat ihn abgelehnt; erneut anfragen), `not_needed` (der Netzbetreiber nimmt Anmeldungen per E-Mail an). `installer` ist null, wenn Ihr Konto selbst der Betrieb ist. `case.operator_login_missing` kommt, wenn ein Fall in `missing` oder `unusable` wechselt.",
            "properties": {
              "operator": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "key": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "key",
                  "name"
                ]
              },
              "installer": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "ref": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "ref",
                  "name"
                ]
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "missing",
                  "held",
                  "unusable",
                  "not_needed",
                  null
                ]
              },
              "link": {
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "operator",
              "installer",
              "status",
              "link"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Auf welcher Umgebung dieser Fall liegt, entschieden durch den API-Schlüssel, der ihn erstellt hat, und danach nie geändert. Die ID sagt dasselbe (`anm_…` gegenüber `anm_test_…`), und es zweimal zu sagen ist Absicht: ein Screenshot eines Sandbox-Falls soll unverwechselbar sein.\n\nEin Schlüssel des einen Modus kann einen Fall des anderen weder lesen noch ändern, einreichen oder verwerfen. Das wird an der ID abgelehnt, vor jeder Suche, sodass eine falsch verdrahtete Umgebung ein 404 ist statt einer halb fertigen Einreichung."
          },
          "kind": {
            "type": "string",
            "enum": [
              "section_14a",
              "pv",
              "storage"
            ],
            "description": "Die Einreichungsfamilie, durch den Erstellpfad festgelegt und nie geändert. Sie entscheidet, welche Fragen der Fall beantworten muss.\n\nEINE FAMILIE IST KEIN GERÄT. `section_14a` ist eine Familie, die vier Geräte abdeckt; `pv` ist eine andere: Erzeugung, ganz ohne steuerbare Verbrauchseinrichtung und mit Vergütungsfeldern, die § 14a nie berührt. Sie ist auch nicht dasselbe wie die Einreichungen: eine § 14a-Erfassung kann weiterhin zu mehr als einer Einreichung führen."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "extracting",
              "needs_input",
              "ready",
              "filing",
              "submitted",
              "confirmed",
              "rejected",
              "cancelled"
            ],
            "description": "draft: erstellt, noch nichts gelesen. extracting: Material wird gelesen. needs_input: etwas Blockierendes ist offen oder widersprüchlich. ready: nichts blockiert. filing: wir reichen ein. submitted: beim Netzbetreiber eingereicht, und weiterhin so, während der Kunde unterschreibt und der Netzbetreiber prüft. confirmed: der Netzbetreiber hat die Anmeldung bestätigt. rejected: der Netzbetreiber hat abgelehnt. cancelled: vor der Einreichung verworfen.\n\nBewusst grob. Die feinere Stufe des Netzbetreibers (`waiting_operator`, `info_needed_installer`, ...) reist in den `case.status_changed`-Ereignissen, wo eine Änderung einen Webhook wert ist; der Fall selbst beantwortet die eine Frage, die ein Bildschirm stellt, nämlich ob er fertig ist und wie."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "operator": {
            "type": [
              "object",
              "null"
            ],
            "description": "Der Netzbetreiber, für den dieser Fall bestimmt ist. Null, bis einer genannt ist. Er kommt aus dem `operator`-Feld, das Sie beim Erstellen senden, nie aus der Adresse geraten: ein falsch geratener Netzbetreiber ändert, welche Fragen der Fall beantworten muss.",
            "properties": {
              "key": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "key",
              "name"
            ]
          },
          "signature": {
            "type": [
              "object",
              "null"
            ],
            "description": "Wo die Unterschrift steht. Null, bis eine angefordert wird.\n\n**`file` erledigt die Aufgabe nicht.** Zwei Unterschriften auf dem § 14a-Formular sind nie unsere (die des Kunden und die der eingetragenen verantwortlichen Fachkraft), deshalb erzeugt das Einreichen das Dokument und leitet es per E-Mail zur Unterschrift weiter. Ein Aufrufer, der nur `submitted` sähe, würde seinem Nutzer sagen, die Einreichung sei raus, während sie auf den Endkunden wartet.\n\n`sent_as` ist der From-Header, wie er tatsächlich gesendet wurde, und `sent_as_kind` sagt, wessen Domain das war. Eine Firma mit verifizierter Sendedomain sendet als sie selbst, denn der Endkunde kennt seinen Elektriker und kennt uns nicht; alle anderen senden als unsere. So oder so landet die Antwort in einem Postfach, das wir lesen, damit das unterschriebene Formular zum Fall zurückkommt.\n\nZustände: `pending` (in der Warteschlange), `sent`, `simulated` (die Sandbox hat den Punkt des Sendens erreicht und bewusst nicht gesendet: der Link funktioniert trotzdem), `failed` (mit `failure_reason`), `opened`, `signed`, `superseded` (eine neuere Anfrage hat sie ersetzt, und dieser Link funktioniert nicht mehr).\n\nAuf einem Live-Fall steht der Link selbst nie in diesem Objekt. Er ist eine Berechtigung: wer ihn hält, kann das Formular des Kunden herunterladen, bis er abläuft, und ein Antwortbody ist ein Ort, an dem er protokolliert würde. Ein Sandbox-Fall trägt `link`, denn dort ist das Formular eine Probe mit Wasserzeichen und Sie sind der Kunde: öffnen Sie ihn, sehen Sie, was er sehen würde, und unterschreiben Sie, um den Rest der Sequenz zu durchlaufen.",
            "properties": {
              "link": {
                "type": "string",
                "format": "uri",
                "description": "NUR SANDBOX. Der Link des Kunden zum Formular, wie die Signatur-E-Mail ihn tragen würde. Auf einem Live-Fall nie vorhanden."
              },
              "state": {
                "type": "string",
                "enum": [
                  "pending",
                  "sent",
                  "simulated",
                  "failed",
                  "opened",
                  "signed",
                  "superseded"
                ]
              },
              "sent_as": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sent_as_kind": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "own",
                  "house",
                  null
                ]
              },
              "requested_at": {
                "type": "string",
                "format": "date-time"
              },
              "sent_at": {
                "type": "string",
                "format": "date-time"
              },
              "opened_at": {
                "type": "string",
                "format": "date-time"
              },
              "expires_at": {
                "type": "string",
                "format": "date-time"
              },
              "failure_reason": {
                "type": "string"
              }
            },
            "required": [
              "state",
              "requested_at",
              "expires_at"
            ]
          },
          "requirements": {
            "type": "object",
            "description": "Unter welchen Anforderungen dieser API-Schlüssel beantwortet wird, und wie viel von den eigenen des Netzbetreibers noch aussteht.\n\n`surface` ist `universal` oder `operator`, und es ist eine Eigenschaft Ihres SCHLÜSSELS, nicht einer Anfrage. Unter `universal` sehen Sie den nationalen Datensatz und sonst nichts, sodass Ihre Integration für jeden Netzbetreiber in Deutschland identisch ist und wir hinter dem herlaufen, was dieser Netzbetreiber zusätzlich fragt. Unter `operator` werden diese Fragen zu `open[]` hinzugefügt, markiert als `operator_specific`, sodass Sie alles in einem Durchgang erfassen können; der Preis ist, dass Ihre Feldliste nun je nach Netzbetreiber variiert.\n\n`operator_outstanding` zählt unter beiden dasselbe, und deshalb kann ein universeller Schlüssel einem leeren `open[]` trauen: es sind die Pflichtfragen des Netzbetreibers, die noch keine Antwort haben. Solange es über null liegt, kann der Fall nicht eingereicht werden, ob Sie die Fragen sehen können oder nicht. Bitten Sie um einen netzbetreiberbezogenen Schlüssel, wenn Sie sie lieber selbst beantworten.",
            "properties": {
              "surface": {
                "type": "string",
                "enum": [
                  "universal",
                  "operator"
                ]
              },
              "operator_outstanding": {
                "type": "integer"
              }
            },
            "required": [
              "surface",
              "operator_outstanding"
            ]
          },
          "price": {
            "type": "object",
            "properties": {
              "credits": {
                "type": "integer"
              },
              "charged": {
                "type": "boolean"
              },
              "charged_at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "credits",
              "charged"
            ],
            "description": "Eine Anlage kostet einen Credit, berechnet beim Einreichen des Falls, nicht solange er ein Entwurf ist."
          },
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResolvedField"
            }
          },
          "open": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpenField"
            }
          },
          "conflicts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Conflict"
            }
          },
          "rejected": {
            "type": "array",
            "description": "Von Ihnen gesendete Werte, die nicht verwendet werden konnten, jeder mit dem Grund. Ein Feld, das die Gerätefamilie nicht hat, ein Wert, der sich nicht als sein Typ parsen lässt, einer, der zweimal unter zwei Namen mit zwei Antworten geliefert wurde. Der Fall wird trotzdem erstellt (der Rest Ihrer Deklaration ist in Ordnung), und nichts hier wird still verworfen.",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "reason"
              ]
            }
          },
          "materials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MaterialSummary"
            }
          }
        },
        "required": [
          "id",
          "mode",
          "status",
          "created_at",
          "updated_at",
          "price",
          "requirements"
        ]
      },
      "Problem": {
        "type": "object",
        "description": "Problem-Detail nach RFC 9457. `code` ist ein geschlossenes Enum und das Feld, nach dem zu verzweigen ist.",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "enum": [
              "unauthenticated",
              "forbidden_scope",
              "not_found",
              "validation_failed",
              "idempotency_conflict",
              "no_credits",
              "rate_limited",
              "material_too_large",
              "unsupported_media_type",
              "case_already_filed",
              "case_incomplete",
              "internal"
            ]
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "case": {
            "type": "string",
            "description": "Der Fall, den diese Ablehnung hinterlassen hat, wenn der Aufruf einen angelegt und dann abgelehnt hat. `POST /v1/anmeldungen/{typ}` mit `intent: \"file\"` legt den Fall an und reicht ihn dann ein, und beides ist nicht ein Akt: Wird die EINREICHUNG abgelehnt (keine Credits, die Sandbox-Grenze), bleibt der Fall als Entwurf bestehen. Schließen Sie ihn mit `POST /v1/anmeldungen/{id}/file` ab, statt den Anlege-Aufruf erneut zu senden, was einen zweiten Fall eröffnen würde. Fehlt bei einer Ablehnung, die nichts angelegt hat."
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ]
      },
      "CreateRequest": {
        "type": "object",
        "description": "Mindestens eines von `declared` und `material` muss vorhanden sein. Beides zu senden ist der Regelfall, und sie ergänzen sich: Material füllt, was declared nicht angegeben hat.",
        "properties": {
          "external_ref": {
            "type": "string",
            "maxLength": 120
          },
          "installer_ref": {
            "type": "string",
            "maxLength": 120,
            "description": "Ihre eigene feste Kennung für den Installationsbetrieb, für den dieser Fall angemeldet wird, wenn ein Konto für viele anmeldet (eine Plattform). Seine Zugänge bei Netzbetreibern gehören nur ihm: nie einem anderen Betrieb, nie Ihrem eigenen Konto. Exakt verglichen, senden Sie also für denselben Betrieb immer dieselbe Kennung. Weglassen, wenn Ihr Konto selbst der Betrieb ist."
          },
          "operator": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]{0,62}$",
            "description": "Der Schlüssel des Netzbetreibers, zum Beispiel `hen` für Hamburger Energienetze. Optional: ohne einen wird der Fall allein gegen den nationalen Datensatz beurteilt. Mit einem kommen die eigenen Anforderungen dieses Netzbetreibers in den Umfang; siehe `requirements` auf dem Fall. `GET /v1/anmeldungen/14a/fields` listet die Felder für die Oberfläche Ihres Schlüssels.\n\n`sandbox` ist ein fiktiver Netzbetreiber mit einem Formular, das sich nie ändert, sodass eine Sandbox-Integration nicht an dem Tag bricht, an dem ein echter Netzbetreiber seines neu herausgibt. Nur Sandbox-Schlüssel; eine Live-Einreichung an einen Netzbetreiber, den es nicht gibt, könnte nie gesendet werden. Echte Netzbetreiber bleiben in der Sandbox wählbar für jeden, der die heutigen Anforderungen will."
          },
          "intent": {
            "type": "string",
            "enum": [
              "draft",
              "file"
            ],
            "default": "draft",
            "description": "`draft` kostet nichts und gibt zurück, was noch offen ist. `file` reicht den Fall ein, wenn nichts Blockierendes offen ist, und gibt andernfalls `needs_input` zurück, ohne einen Credit zu berechnen. Einen unvollständigen Fall einreichen zu wollen ist kein Fehler.\n\nDer Fall wird zuerst angelegt und dann eingereicht, sodass eine Ablehnung allein der EINREICHUNG (keine Credits, die Sandbox-Grenze) den Fall als Entwurf bestehen lässt. Der Problem-Body trägt dann seine id in `case`: Schließen Sie ihn mit `POST /v1/anmeldungen/{id}/file` ab, statt den Anlege-Aufruf erneut zu senden."
          },
          "declared": {
            "$ref": "#/components/schemas/Declared"
          },
          "material": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Material"
            },
            "maxItems": 30
          },
          "sandbox_outcome": {
            "type": "string",
            "enum": [
              "accepted",
              "rejected",
              "needs_more_information"
            ],
            "default": "accepted",
            "description": "Mit `intent: file`, genau wie auf `POST /{id}/file`. NUR SANDBOX. Was der Netzbetreiber entscheidet. Würde die Sandbox immer annehmen, baute niemand den Ablehnungspfad; also wählen Sie, und der Fall landet in derselben Stufe wie ein Live-Fall, mit einem genannten Grund.\n\nUnter einem Live-Schlüssel ist dies ein 422, der das Feld benennt, kein Ignorieren. Das Ergebnis einer Live-Einreichung kommt vom Netzbetreiber."
          },
          "sandbox_pace": {
            "type": "string",
            "enum": [
              "instant",
              "staged"
            ],
            "default": "instant",
            "description": "Mit `intent: file`, genau wie auf `POST /{id}/file`. NUR SANDBOX. Wie schnell der Sandbox-Netzbetreiber antwortet.\n\n`instant` antwortet innerhalb des Einreichaufrufs, was eine Testsuite will. `staged` ist das eine, was eine sofortige Antwort nicht zeigen kann: ein Live-Fall wird eingereicht, wartet und wird später beantwortet. Staged durchläuft `submitted`, `waiting_operator` und dann `sandbox_outcome` im Abstand weniger Sekunden auf einem Timer, jeder Schritt ein `case.status_changed`-Ereignis, das Ihr Webhook der Reihe nach erhält, sodass ein Empfänger, der die Antwort bisher nur innerhalb des Einreichaufrufs gesehen hat, das Warten testen kann.\n\nUnter einem Live-Schlüssel ist dies ein 422, der das Feld benennt. Eine Live-Einreichung wird beantwortet, wenn der Netzbetreiber antwortet."
          }
        },
        "additionalProperties": false
      },
      "PatchRequest": {
        "type": "object",
        "description": "Ergänzt oder korrigiert deklarierte Werte. Führt zusammen statt zu ersetzen: ein weggelassenes Feld bleibt unberührt. Null zu senden setzt es auf das zurück, was die Erkennung gefunden hat.",
        "properties": {
          "external_ref": {
            "type": "string"
          },
          "installer_ref": {
            "type": "string",
            "maxLength": 120,
            "description": "Nennt oder ändert den Installationsbetrieb, für den der Fall angemeldet wird, wie beim Anlegen. Anders als `declared` darf es auch für einen eingereichten Fall gesendet werden: der Zugang des Betriebs wird nach der Einreichung verwendet."
          },
          "declared": {
            "$ref": "#/components/schemas/Declared"
          }
        },
        "additionalProperties": false
      },
      "InformationMissing": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string"
          },
          "label": {
            "$ref": "#/components/schemas/Localised"
          },
          "help": {
            "$ref": "#/components/schemas/Localised"
          },
          "type": {
            "type": "string",
            "enum": [
              "field",
              "document",
              "text"
            ]
          },
          "required": {
            "type": "boolean"
          },
          "owed_by": {
            "type": "string",
            "enum": [
              "installer",
              "end_customer"
            ]
          }
        },
        "required": [
          "path",
          "label",
          "type",
          "required",
          "owed_by"
        ]
      },
      "InformationRequest": {
        "type": "object",
        "description": "Eine unveränderliche Anforderung des Netzbetreibers nach der Einreichung. Eine spätere Rückfrage ist eine neue Sequenz, niemals eine Änderung dieser Anforderung.",
        "properties": {
          "id": {
            "type": "string",
            "description": "`info_…` live, `info_test_…` Sandbox."
          },
          "sequence": {
            "type": "integer",
            "minimum": 1
          },
          "revision": {
            "type": "integer",
            "minimum": 1
          },
          "source": {
            "type": "string",
            "enum": [
              "operator",
              "sandbox"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "open",
              "processing",
              "ready_to_submit",
              "queued_for_operator",
              "resolved",
              "cancelled"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "missing": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InformationMissing"
            }
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "opened_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "closed_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "sequence",
          "revision",
          "source",
          "state",
          "active",
          "missing",
          "note",
          "opened_at",
          "updated_at"
        ]
      },
      "InformationRequestSnapshot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`info_…` live, `info_test_…` Sandbox."
          },
          "sequence": {
            "type": "integer",
            "minimum": 1
          },
          "revision": {
            "type": "integer",
            "minimum": 1
          },
          "source": {
            "type": "string",
            "enum": [
              "operator",
              "sandbox"
            ]
          },
          "missing": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InformationMissing"
            }
          }
        },
        "required": [
          "id",
          "sequence",
          "revision",
          "source",
          "missing"
        ]
      },
      "InformationResponseRequest": {
        "type": "object",
        "description": "Es werden nur die Feldpfade und Materialarten angenommen, die diese Informationsanforderung nennt. Mindestens eines von `declared` oder `material` ist erforderlich.",
        "properties": {
          "declared": {
            "$ref": "#/components/schemas/Declared"
          },
          "material": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Material"
            },
            "maxItems": 30
          }
        },
        "additionalProperties": false
      },
      "InformationResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`resp_…` live, `resp_test_…` Sandbox."
          },
          "state": {
            "type": "string",
            "enum": [
              "processing",
              "ready_to_submit",
              "queued_for_operator",
              "submitted"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "state",
          "created_at",
          "updated_at"
        ]
      }
    },
    "examples": {
      "CreateSandbox": {
        "summary": "Fixture: eine gehostete Rechnung, kostenlos, kein Modellaufruf",
        "description": "Senden Sie diesen zuerst. `fixture` benennt ein Dokument, das wir hosten und bereits gelesen haben; es wird also nichts hochgeladen und kein Modellaufruf für das Lesen berechnet, und ein Bildschirm im Bau kann den ganzen Tag dagegen neu bauen.\n\nDas Ergebnis ist ein echter Fall in der Sandbox-Umgebung, mit denselben Feldern, denselben Zitaten und derselben Form, die ein Live-Aufruf liefert. Nichts daran kann einreichen, berechnen oder jemanden anmailen.",
        "value": {
          "external_ref": "ERP-4711",
          "intent": "draft",
          "material": [
            {
              "fixture": "wallbox-invoice-de"
            }
          ]
        }
      },
      "MaterialUploaded": {
        "summary": "Hochgeladen: die Speicher-ID, mit der ein Upload-Ziel geantwortet hat",
        "value": {
          "material": [
            {
              "kind": "file",
              "storage_id": "kg21x9q8v0s5c7b2m4n6p8r0t2w4y6a",
              "filename": "rechnung.pdf",
              "content_type": "application/pdf"
            }
          ]
        }
      },
      "MaterialInline": {
        "summary": "Inline: eine kleine Datei als base64 in `data`",
        "description": "Das `data` hier ist ein PDF-Header und nichts danach: gültiges base64, viel zu wenig für ein Dokument.",
        "value": {
          "material": [
            {
              "kind": "file",
              "filename": "typenschild.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQK"
            }
          ]
        }
      },
      "MaterialText": {
        "summary": "Text: eine eingefügte Kundenmail",
        "value": {
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo, anbei die Unterlagen zur PV-Anlage. Die Module sind seit Juni montiert.\n\nViele Gruesse\nInes Kaufmann"
            }
          ]
        }
      },
      "CreatePvFixture": {
        "summary": "Fixture: eine gehostete PV-Rechnung, kostenlos, kein Modellaufruf",
        "value": {
          "external_ref": "ERP-5101",
          "intent": "draft",
          "material": [
            {
              "fixture": "pv-invoice-de"
            }
          ]
        }
      },
      "CreatePvRaw": {
        "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
        "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
        "value": {
          "external_ref": "ERP-5103",
          "intent": "draft",
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo,\n\nanbei die Rechnung und ein Foto vom Typenschild des Wechselrichters zu unserer neuen PV-Anlage (9,84 kWp, ein Wechselrichter Fronius Symo GEN24 10.0 Plus). Zaehlernummer ist 1ESY1189004412. Bitte melden Sie die Anlage beim Netzbetreiber an, wir moechten die Einspeiseverguetung nach EEG.\n\nViele Gruesse\nInes Kaufmann\nHeinrichstrasse 118, 64283 Darmstadt"
            },
            {
              "kind": "file",
              "filename": "rechnung-sonnenstrom.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQK"
            },
            {
              "kind": "file",
              "filename": "typenschild-wechselrichter.jpg",
              "content_type": "image/jpeg",
              "data": "/9j/4AAQSkZJRgAB"
            }
          ]
        }
      },
      "CreatePvDeclared": {
        "summary": "Deklariert: Felder aus Ihrem CRM, ein Wechselrichter, 9,84 kWp",
        "value": {
          "external_ref": "ERP-5102",
          "intent": "draft",
          "declared": {
            "site": {
              "street": "Heinrichstrasse",
              "house_number": "118",
              "postal_code": "64283",
              "city": "Darmstadt"
            },
            "end_customer": {
              "first_name": "Ines",
              "last_name": "Kaufmann",
              "email": "i.kaufmann@example.de",
              "phone": "+49 6151 5550188",
              "is_owner": true
            },
            "installer": {
              "company": "Sonnenstrom Hessen GmbH",
              "street": "Rheinstrasse",
              "house_number": "40",
              "postal_code": "64293",
              "city": "Darmstadt",
              "email": "info@sonnenstrom-hessen.example"
            },
            "device": {
              "inverter_count": 1,
              "apparent_power_kva": 10,
              "active_power_kw": 10,
              "module_power_kwp": 9.84,
              "installed_at": "2026-06-12",
              "status": "planned"
            },
            "inverters": [
              {
                "manufacturer": "Fronius",
                "model": "Symo GEN24 10.0 Plus",
                "apparent_power_kva": 10,
                "active_power_kw": 10
              }
            ],
            "protection": {
              "manufacturer": "Ziehl",
              "model": "UFR1001E"
            },
            "connection": {
              "meter_number": "1ESY1189004412"
            },
            "feed_in": {
              "sale_form": "feed_in_tariff"
            }
          }
        }
      },
      "CreateSpeicherFixture": {
        "summary": "Fixture: eine gehostete Speicherrechnung, kostenlos, kein Modellaufruf",
        "value": {
          "external_ref": "ERP-5201",
          "intent": "draft",
          "material": [
            {
              "fixture": "battery-invoice-de"
            }
          ]
        }
      },
      "CreateSpeicherRaw": {
        "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
        "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
        "value": {
          "external_ref": "ERP-5203",
          "intent": "draft",
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo,\n\nim Anhang Rechnung und Typenschild zu unserem Stromspeicher (BYD Battery-Box Premium HVS 10.2, 10,24 kWh, eigener Wechselrichter). Zaehlernummer 1ESY1170551234. Der Speicher soll nach 14a EnWG mit Modul 1 angemeldet werden.\n\nViele Gruesse\nJonas Petersen\nKanalufer 9, 24106 Kiel"
            },
            {
              "kind": "file",
              "filename": "rechnung-nordkap.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQK"
            },
            {
              "kind": "file",
              "filename": "typenschild-speicher.jpg",
              "content_type": "image/jpeg",
              "data": "/9j/4AAQSkZJRgAB"
            }
          ]
        }
      },
      "CreateSpeicherDeclared": {
        "summary": "Deklariert: Felder aus Ihrem CRM, 10,24 kWh, AC-gekoppelt",
        "value": {
          "external_ref": "ERP-5202",
          "intent": "draft",
          "declared": {
            "site": {
              "street": "Kanalufer",
              "house_number": "9",
              "postal_code": "24106",
              "city": "Kiel"
            },
            "end_customer": {
              "first_name": "Jonas",
              "last_name": "Petersen",
              "email": "j.petersen@example.de",
              "phone": "+49 431 5550172",
              "is_owner": true
            },
            "installer": {
              "company": "Elektro Nordkap GmbH",
              "email": "kontakt@elektro-nordkap.example"
            },
            "device": {
              "count": 1,
              "manufacturer": "BYD",
              "model": "Battery-Box Premium HVS 10.2",
              "apparent_power_kva": 5,
              "active_power_kw": 5,
              "storage_capacity_kwh": 10.24,
              "coupling": "Eigener Wechselrichter (AC-Kopplung)",
              "installed_at": "2026-07-02",
              "status": "planned"
            },
            "section_14a": {
              "is_controllable": true,
              "control_responsibility": "metering_operator",
              "modules": "1",
              "consent": {
                "given_by": "end_customer",
                "given_at": "2026-07-01T09:30:00Z",
                "channel": "email"
              }
            },
            "connection": {
              "meter_number": "1ESY1170551234"
            }
          }
        }
      },
      "CreateWaermepumpeFixture": {
        "summary": "Fixture: eine gehostete Wärmepumpenrechnung, kostenlos, kein Modellaufruf",
        "value": {
          "external_ref": "ERP-5301",
          "intent": "draft",
          "material": [
            {
              "fixture": "heat-pump-invoice-de"
            }
          ]
        }
      },
      "CreateWaermepumpeRaw": {
        "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
        "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
        "value": {
          "external_ref": "ERP-5303",
          "intent": "draft",
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo,\n\nanbei Rechnung und Typenschild unserer Waermepumpe (Vaillant aroTHERM plus VWL 75/6 A, Luft-Wasser). Sie laeuft seit Mai. Zaehlernummer 1ESY1170099887. Bitte Modul 1 nach 14a EnWG anmelden, die Steuerung uebernimmt der Netzbetreiber.\n\nViele Gruesse\nPetra Ahlers\nWiesenweg 3, 22607 Hamburg"
            },
            {
              "kind": "file",
              "filename": "rechnung-nordlicht.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQK"
            },
            {
              "kind": "file",
              "filename": "typenschild-waermepumpe.jpg",
              "content_type": "image/jpeg",
              "data": "/9j/4AAQSkZJRgAB"
            }
          ]
        }
      },
      "CreateWaermepumpeDeclared": {
        "summary": "Deklariert: Felder aus Ihrem CRM, Luft-Wasser, 12 A Anlaufstrom",
        "value": {
          "external_ref": "ERP-5302",
          "intent": "draft",
          "declared": {
            "site": {
              "street": "Wiesenweg",
              "house_number": "3",
              "postal_code": "22607",
              "city": "Hamburg"
            },
            "end_customer": {
              "first_name": "Petra",
              "last_name": "Ahlers",
              "email": "p.ahlers@example.de",
              "phone": "+49 40 5550199",
              "is_owner": true
            },
            "installer": {
              "company": "Sanitaer & Waerme Nordlicht GmbH",
              "email": "buero@nordlicht-waerme.example"
            },
            "device": {
              "count": 1,
              "manufacturer": "Vaillant",
              "model": "aroTHERM plus VWL 75/6 A",
              "heat_pump_type": "air_water",
              "starting_current_a": 12,
              "installed_at": "2026-05-20",
              "status": "installed"
            },
            "section_14a": {
              "control_responsibility": "grid_operator",
              "modules": "1",
              "consent": {
                "given_by": "end_customer",
                "given_at": "2026-05-18T09:30:00Z",
                "channel": "phone"
              }
            },
            "connection": {
              "meter_number": "1ESY1170099887"
            }
          }
        }
      },
      "CreateLadeeinrichtungFixture": {
        "summary": "Fixture: eine gehostete Wallbox-Rechnung, kostenlos, kein Modellaufruf",
        "value": {
          "external_ref": "ERP-5401",
          "intent": "draft",
          "material": [
            {
              "fixture": "wallbox-invoice-de"
            }
          ]
        }
      },
      "CreateLadeeinrichtungRaw": {
        "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
        "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
        "value": {
          "external_ref": "ERP-5403",
          "intent": "draft",
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo,\n\nanbei die Unterlagen zu meiner Wallbox (Easee Home, 22 kW), Rechnung und Typenschild. Die Anlage laeuft seit 2022, Zaehlernummer 1ESY1161234567. Ich bin Eigentuemer. Bitte Modul 1 nach 14a EnWG anmelden.\n\nViele Gruesse\nThomas Weber\nSchackumer Strasse 20, 40667 Meerbusch"
            },
            {
              "kind": "file",
              "filename": "rechnung-haslinger.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQK"
            },
            {
              "kind": "file",
              "filename": "typenschild-wallbox.jpg",
              "content_type": "image/jpeg",
              "data": "/9j/4AAQSkZJRgAB"
            }
          ]
        }
      },
      "CreateLadeeinrichtungDeclared": {
        "summary": "Deklariert: Felder aus Ihrem CRM, 22 kW, steuerbar",
        "value": {
          "external_ref": "ERP-5402",
          "intent": "draft",
          "declared": {
            "site": {
              "street": "Schackumer Strasse",
              "house_number": "20",
              "postal_code": "40667",
              "city": "Meerbusch"
            },
            "end_customer": {
              "first_name": "Thomas",
              "last_name": "Weber",
              "email": "t.weber@example.de",
              "phone": "+49 2150 5550144",
              "is_owner": true
            },
            "installer": {
              "company": "Elektro Haslinger GmbH",
              "email": "info@elektro-haslinger.example"
            },
            "device": {
              "count": 1,
              "manufacturer": "Easee",
              "model": "Easee Home",
              "grid_draw_kw": 22,
              "installed_at": "2026-04-19",
              "status": "in_operation"
            },
            "section_14a": {
              "is_controllable": true,
              "control_responsibility": "grid_operator",
              "modules": "1",
              "consent": {
                "given_by": "end_customer",
                "given_at": "2026-04-15T09:30:00Z",
                "channel": "email"
              }
            },
            "connection": {
              "meter_number": "1ESY1161234567",
              "has_smart_meter": false
            }
          }
        }
      },
      "CreateKlimaanlageFixture": {
        "summary": "Fixture: eine gehostete Klimaanlagenrechnung, kostenlos, kein Modellaufruf",
        "value": {
          "external_ref": "ERP-5501",
          "intent": "draft",
          "material": [
            {
              "fixture": "air-conditioning-invoice-de"
            }
          ]
        }
      },
      "CreateKlimaanlageRaw": {
        "summary": "Roh: die Kundenmail und zwei Dateien, nichts Strukturiertes",
        "description": "Die Form, die ein Backoffice tatsächlich hat: eine eingefügte Mail und die Anhänge, keine Felder. Jeder Wert kommt als `extracted` zurück, jeder mit dem Zitat, aus dem er gelesen wurde. Die beiden `data`-Werte hier sind ein PDF-Header und ein JPEG-Header und nichts danach: gültiges base64, sendbar, viel zu wenig für Dokumente, sie werden also als unlesbar gelesen, und die Mail trägt den Fall. Senden Sie echte Dateien, und sie werden echt gelesen, 25 pro Tag in der Sandbox.",
        "value": {
          "external_ref": "ERP-5503",
          "intent": "draft",
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo,\n\nanbei Rechnung und Typenschild zu unserer neuen Klimaanlage (Daikin Perfera FTXM50R, 1,6 kW, Split). Zaehlernummer 1ESY1130077890. Keine kritische Infrastruktur, bitte Modul 1 nach 14a EnWG anmelden.\n\nViele Gruesse\nMaria Brandt\nAm Winterhafen 12, 55131 Mainz"
            },
            {
              "kind": "file",
              "filename": "rechnung-klimatechnik.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQK"
            },
            {
              "kind": "file",
              "filename": "typenschild-klimageraet.jpg",
              "content_type": "image/jpeg",
              "data": "/9j/4AAQSkZJRgAB"
            }
          ]
        }
      },
      "CreateKlimaanlageDeclared": {
        "summary": "Deklariert: Felder aus Ihrem CRM, 1,6 kW, keine kritische Infrastruktur",
        "value": {
          "external_ref": "ERP-5502",
          "intent": "draft",
          "declared": {
            "site": {
              "street": "Am Winterhafen",
              "house_number": "12",
              "postal_code": "55131",
              "city": "Mainz"
            },
            "end_customer": {
              "first_name": "Maria",
              "last_name": "Brandt",
              "email": "m.brandt@example.de",
              "phone": "+49 6131 5550166",
              "is_owner": true
            },
            "installer": {
              "company": "Klimatechnik Rhein-Main GmbH",
              "email": "service@klimatechnik-rhein-main.example"
            },
            "device": {
              "count": 1,
              "manufacturer": "Daikin",
              "model": "Perfera FTXM50R",
              "electrical_power_kw": 1.6,
              "starting_current_a": 8.5,
              "grid_draw_kw": 1.6,
              "installed_at": "2026-08-05",
              "status": "planned"
            },
            "section_14a": {
              "is_controllable": true,
              "critical_infrastructure": false,
              "control_responsibility": "metering_operator",
              "modules": "1",
              "consent": {
                "given_by": "end_customer",
                "given_at": "2026-08-01T09:30:00Z",
                "channel": "email"
              }
            },
            "connection": {
              "meter_number": "1ESY1130077890"
            }
          }
        }
      },
      "CreateRawOnly": {
        "summary": "Roh: alles ablegen, nichts deklarieren",
        "description": "Der ganze Sinn des rohen Kanals: das Backoffice eines Installateurs hat eine Kundenmail und drei PDFs und nichts Strukturiertes. Jedes Feld der Antwort ist dann `source: \"extracted\"`, jedes mit dem Zitat, aus dem es gelesen wurde.\n\nFunktioniert auf beiden Umgebungen. Ein von Ihnen geliefertes Dokument zu lesen kostet einen Modellaufruf pro Datei, weshalb die Sandbox höchstens 25 echte Dokumente pro Konto und Tag liest; Fixtures sind dort unbegrenzt.\n\nDie beiden `data`-Werte sind ein PDF-Header und nichts danach: gültiges base64, die Anfrage ist also sendbar, und viel zu wenig für ein Dokument. Bis zum 2026-09-06 trugen sie ein abschließendes `…`, wodurch dieses Beispiel an der Kodierung scheiterte, sobald jemand Send drückte.",
        "value": {
          "external_ref": "ERP-4711",
          "intent": "draft",
          "material": [
            {
              "kind": "text",
              "label": "Kundenmail",
              "content": "Hallo, anbei die Unterlagen zu meiner Wallbox. Die Anlage laeuft seit 2022. Ich bin Eigentuemer. Bitte Modul 1 nach 14a EnWG anmelden.\n\nViele Gruesse\nAlexander Bergmann"
            },
            {
              "kind": "file",
              "filename": "technische-zustimmung.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQKJeLjz9MK"
            },
            {
              "kind": "file",
              "filename": "schlussrechnung.pdf",
              "content_type": "application/pdf",
              "data": "JVBERi0xLjQKJcfsj6IK"
            }
          ]
        }
      },
      "CreateDeclaredOnly": {
        "summary": "Deklariert: alles deklarieren, nichts ablegen",
        "description": "Ein ERP, das die Daten schon hat. Keine Erkennung läuft, der Fall ist beim ersten Aufruf `ready`, und `intent: \"file\"` reicht ihn in einem Durchgang ein.",
        "value": {
          "external_ref": "ERP-4712",
          "intent": "file",
          "declared": {
            "site": {
              "street": "Ahornweg",
              "house_number": "7a",
              "postal_code": "22607",
              "city": "Hamburg"
            },
            "end_customer": {
              "first_name": "Alexander",
              "last_name": "Bergmann",
              "email": "a.bergmann@example.de",
              "phone": "+49 40 5550123",
              "is_owner": true
            },
            "installer": {
              "company": "Nordlicht Elektrotechnik GmbH",
              "contact_first_name": "Jan",
              "contact_last_name": "Petersen",
              "email": "petersen@example.de",
              "registry_number": "HH-EL-04127"
            },
            "device": {
              "category": "charge_point",
              "manufacturer": "ABL",
              "model": "eMH3 3W2250",
              "count": 1,
              "grid_draw_kw": 11,
              "installed_at": "2022-04-19",
              "status": "in_operation"
            },
            "connection": {
              "meter_number": "1ISK0000123456",
              "has_smart_meter": true
            },
            "section_14a": {
              "modules": "1",
              "control_responsibility": "metering_operator",
              "consent": {
                "given_by": "end_customer",
                "given_at": "2026-08-17T14:05:00Z",
                "channel": "phone",
                "reference": "call-2026-08-17-1405"
              }
            }
          }
        }
      },
      "CreateMixed": {
        "summary": "Gemischt: einige Felder deklariert, der Rest in einem PDF",
        "description": "Einige Felder sind bekannt, der Rest steht in einem PDF. Deklarierte Werte werden von der Erkennung nie überschrieben; wo die Erkennung widerspricht, wird der Widerspruch in `conflicts` gemeldet, statt still aufgelöst zu werden.",
        "value": {
          "external_ref": "ERP-4713",
          "intent": "draft",
          "declared": {
            "site": {
              "street": "Ahornweg",
              "house_number": "7a",
              "postal_code": "22607",
              "city": "Hamburg"
            },
            "end_customer": {
              "first_name": "Alexander",
              "last_name": "Bergmann",
              "email": "a.bergmann@example.de"
            },
            "device": {
              "category": "charge_point"
            },
            "section_14a": {
              "modules": "1",
              "consent": {
                "given_by": "end_customer",
                "given_at": "2026-08-17T14:05:00Z",
                "channel": "phone",
                "reference": "call-2026-08-17-1405"
              }
            },
            "nr": {
              "5017": "11"
            }
          },
          "material": [
            {
              "kind": "file",
              "storage_id": "kg21x9…",
              "filename": "schlussrechnung.pdf",
              "content_type": "application/pdf"
            }
          ]
        }
      },
      "PatchResolveConflict": {
        "summary": "Einen Konflikt auflösen, indem der Wert deklariert wird",
        "description": "Einen Wert zu deklarieren entscheidet einen Konflikt, denn declared gewinnt immer. So beantwortet ein Aufrufer \"die Rechnung sagt sieben, der Netzbetreiber hat einen genehmigt\".",
        "value": {
          "declared": {
            "device": {
              "count": 1
            }
          }
        }
      },
      "CaseNeedsInput": {
        "summary": "Erkennung abgeschlossen, zwei Dinge blockieren die Einreichung",
        "description": "Das realistische Ergebnis einer rohen Ablage. Die meisten Felder aus den Dokumenten aufgelöst, ein Pflichtfeld ist noch offen, und die Stückzahl widerspricht sich zwischen zwei Dokumenten.",
        "value": {
          "id": "anm_7Kq3ZmR8vT",
          "external_ref": "ERP-4711",
          "kind": "section_14a",
          "mode": "live",
          "status": "needs_input",
          "created_at": "2026-08-30T09:12:04Z",
          "updated_at": "2026-08-30T09:12:41Z",
          "operator": {
            "key": "hen",
            "name": "Hamburger Energienetze GmbH"
          },
          "requirements": {
            "surface": "universal",
            "operator_outstanding": 0
          },
          "price": {
            "credits": 1,
            "charged": false
          },
          "fields": [
            {
              "path": "site.street",
              "nr": 1002,
              "label": {
                "de": "Straße",
                "en": "Street"
              },
              "value": "Ahornweg",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_7Rb2",
                "filename": "schlussrechnung.pdf",
                "quote": "Ahornweg 7a"
              }
            },
            {
              "path": "site.house_number",
              "nr": 1003,
              "label": {
                "de": "Hausnummer",
                "en": "House number"
              },
              "value": "7a",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_7Rb2",
                "filename": "schlussrechnung.pdf",
                "quote": "Ahornweg 7a"
              }
            },
            {
              "path": "site.postal_code",
              "nr": 1007,
              "label": {
                "de": "PLZ",
                "en": "Postal code"
              },
              "value": "22607",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_3Kd8",
                "filename": "technische-zustimmung.pdf",
                "quote": "Anlagenadresse: Ahornweg 7a, 22607 Hamburg"
              }
            },
            {
              "path": "site.city",
              "nr": 1008,
              "label": {
                "de": "Ort",
                "en": "City"
              },
              "value": "Hamburg",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_3Kd8",
                "filename": "technische-zustimmung.pdf",
                "quote": "Anlagenadresse: Ahornweg 7a, 22607 Hamburg"
              }
            },
            {
              "path": "site.address_kind",
              "nr": 1001,
              "label": {
                "de": "Art der Standortangabe",
                "en": "Kind of location detail"
              },
              "value": "postalische Adresse",
              "source": "derived",
              "confidence": "certain"
            },
            {
              "path": "end_customer.last_name",
              "nr": 1101,
              "label": {
                "de": "Name oder Firmenname",
                "en": "Name or company"
              },
              "value": "Bergmann",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_7Rb2",
                "filename": "schlussrechnung.pdf",
                "quote": "Herrn Alexander Bergmann"
              }
            },
            {
              "path": "end_customer.first_name",
              "nr": 1102,
              "label": {
                "de": "Vorname",
                "en": "First name"
              },
              "value": "Alexander",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_7Rb2",
                "filename": "schlussrechnung.pdf",
                "quote": "Herrn Alexander Bergmann"
              }
            },
            {
              "path": "installer.company",
              "nr": 1402,
              "label": {
                "de": "Firmenname",
                "en": "Company name"
              },
              "value": "Nordlicht Elektrotechnik GmbH",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_7Rb2",
                "filename": "schlussrechnung.pdf",
                "quote": "Nordlicht Elektrotechnik GmbH · Gewerbestraße 12"
              }
            },
            {
              "path": "device.manufacturer",
              "nr": 5002,
              "label": {
                "de": "Hersteller",
                "en": "Manufacturer"
              },
              "value": "ABL",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_3Kd8",
                "filename": "technische-zustimmung.pdf",
                "quote": "ABL eMH3 3W2250"
              }
            },
            {
              "path": "device.model",
              "nr": 5003,
              "label": {
                "de": "Typ",
                "en": "Type"
              },
              "value": "eMH3 3W2250",
              "source": "extracted",
              "confidence": "certain",
              "evidence": {
                "material_id": "mat_3Kd8",
                "filename": "technische-zustimmung.pdf",
                "quote": "ABL eMH3 3W2250"
              }
            },
            {
              "path": "device.grid_draw_kw",
              "nr": 5017,
              "label": {
                "de": "Netzbezugsleistung",
                "en": "Grid draw"
              },
              "value": 11,
              "source": "extracted",
              "confidence": "certain",
              "unit": "kW",
              "evidence": {
                "material_id": "mat_3Kd8",
                "filename": "technische-zustimmung.pdf",
                "quote": "Freigegebene Leistung: 11 kW"
              }
            },
            {
              "path": "section_14a.is_controllable",
              "nr": 5010,
              "label": {
                "de": "Steuerbare Verbrauchseinrichtung nach § 14a EnWG",
                "en": "Controllable consumption device under § 14a EnWG"
              },
              "value": "Ja",
              "source": "declared",
              "confidence": "certain"
            },
            {
              "path": "device.installed_at",
              "label": {
                "de": "Inbetriebnahmedatum",
                "en": "Commissioning date"
              },
              "value": "2022-04-19",
              "source": "extracted",
              "confidence": "probable",
              "evidence": {
                "material_id": "mat_7Rb2",
                "filename": "schlussrechnung.pdf",
                "quote": "Liefertermin: 19.04.22"
              }
            }
          ],
          "open": [
            {
              "path": "end_customer.phone",
              "nr": 1109,
              "label": {
                "de": "Telefon",
                "en": "Phone"
              },
              "help": {
                "de": "Tragen Sie die Telefonnummer des Anschlussnutzers ein.",
                "en": "Enter the phone number of the connection user."
              },
              "type": "string",
              "owed_by": "end_customer",
              "blocking": true
            },
            {
              "path": "section_14a.control_responsibility",
              "nr": 2010,
              "label": {
                "de": "Steuerbarkeit",
                "en": "Controllability"
              },
              "help": {
                "de": "Wählen Sie aus, wer mit der Herstellung der Steuerbarkeit beauftragt wird. Hinweis: für die Bereitstellung und Anbindung des Steuergerätes (Steuerbox) an der Übergabestelle Netz zur Kundenanlage ist der Messtellenbetreiber zuständig.",
                "en": "State who is responsible for grid-oriented control."
              },
              "type": "enum",
              "options": [
                {
                  "value": "grid_operator",
                  "label": {
                    "de": "Netzbetreiber",
                    "en": "Grid operator"
                  }
                },
                {
                  "value": "metering_operator",
                  "label": {
                    "de": "Messstellenbetreiber",
                    "en": "Metering point operator"
                  }
                }
              ],
              "owed_by": "us",
              "blocking": true,
              "hint": {
                "de": "Ein intelligentes Messsystem ist vorhanden, daher ist der Messstellenbetreiber wahrscheinlich.",
                "en": "An intelligent metering system is present, so the metering point operator is likely."
              }
            },
            {
              "path": "installer.registry_number",
              "nr": 1411,
              "label": {
                "de": "Eintragungsnummer",
                "en": "Registry number"
              },
              "help": {
                "de": "Tragen Sie die Eintragungsnummer des Elektrofachbetriebs ein.",
                "en": "Enter the registry number of the electrical contractor."
              },
              "type": "string",
              "owed_by": "installer",
              "blocking": false
            }
          ],
          "conflicts": [
            {
              "path": "device.count",
              "nr": 5001,
              "label": {
                "de": "Anzahl Ladeeinrichtungen",
                "en": "Number of charge points"
              },
              "blocking": true,
              "candidates": [
                {
                  "value": 1,
                  "source": "extracted",
                  "evidence": {
                    "material_id": "mat_3Kd8",
                    "filename": "technische-zustimmung.pdf",
                    "quote": "ABL eMH3 3W2250 | 1 | 11 kW | AC"
                  }
                },
                {
                  "value": 7,
                  "source": "extracted",
                  "evidence": {
                    "material_id": "mat_7Rb2",
                    "filename": "schlussrechnung.pdf",
                    "quote": "1 x eMH3 2260 B, 6 x eMH3 2250 B Slave"
                  }
                }
              ],
              "message": {
                "de": "Die Zustimmung des Netzbetreibers nennt eine Ladeeinrichtung, die Rechnung sieben. Bitte bestätigen, wie viele an diesem Netzanschluss betrieben werden.",
                "en": "The grid operator's approval names one charge point, the invoice seven. Please confirm how many run on this connection."
              }
            }
          ],
          "notices": [
            {
              "code": "existing_operator_approval_found",
              "message": {
                "de": "Für diese Anlage liegt bereits eine Zustimmung des Netzbetreibers vor. Ein Netzanschlussbegehren wird deshalb nicht eingereicht.",
                "en": "An approval from the grid operator already exists for this installation, so no connection request will be filed."
              },
              "detail": {
                "operator_reference": "500123",
                "approved_at": "2022-03-11"
              }
            },
            {
              "code": "existing_installation",
              "message": {
                "de": "Die Anlage wurde vor dem 01.01.2024 in Betrieb genommen und wird als Bestandsanlage angemeldet.",
                "en": "The installation was commissioned before 2024-01-01 and is registered as an existing installation."
              },
              "detail": {
                "installed_at": "2022-04-19"
              }
            }
          ],
          "materials": [
            {
              "id": "mat_3Kd8",
              "kind": "file",
              "filename": "technische-zustimmung.pdf",
              "content_type": "application/pdf",
              "bytes": 181012,
              "status": "read",
              "extracted_chars": 5346
            },
            {
              "id": "mat_7Rb2",
              "kind": "file",
              "filename": "schlussrechnung.pdf",
              "content_type": "application/pdf",
              "bytes": 374756,
              "status": "read",
              "extracted_chars": 6236
            },
            {
              "id": "mat_1Qa9",
              "kind": "text",
              "label": "Kundenmail",
              "status": "read",
              "extracted_chars": 168
            }
          ],
          "filings": [],
          "last_event_seq": 2
        }
      },
      "CaseReady": {
        "summary": "Nichts blockiert, wartet auf die Entscheidung zur Einreichung",
        "value": {
          "id": "anm_7Kq3ZmR8vT",
          "external_ref": "ERP-4711",
          "kind": "section_14a",
          "mode": "live",
          "status": "ready",
          "created_at": "2026-08-30T09:12:04Z",
          "updated_at": "2026-08-30T09:31:10Z",
          "operator": {
            "key": "hen",
            "name": "Hamburger Energienetze GmbH"
          },
          "requirements": {
            "surface": "universal",
            "operator_outstanding": 0
          },
          "price": {
            "credits": 1,
            "charged": false
          },
          "open": [
            {
              "path": "installer.registry_number",
              "nr": 1411,
              "label": {
                "de": "Eintragungsnummer",
                "en": "Registry number"
              },
              "type": "string",
              "owed_by": "installer",
              "blocking": false
            }
          ],
          "conflicts": [],
          "notices": [],
          "filings": [],
          "last_event_seq": 5
        }
      },
      "CaseFiled": {
        "summary": "Eingereicht, der Netzbetreiber bearbeitet",
        "value": {
          "id": "anm_7Kq3ZmR8vT",
          "external_ref": "ERP-4711",
          "kind": "section_14a",
          "mode": "live",
          "status": "submitted",
          "created_at": "2026-08-30T09:12:04Z",
          "updated_at": "2026-08-30T09:33:02Z",
          "operator": {
            "key": "hen",
            "name": "Hamburger Energienetze GmbH"
          },
          "requirements": {
            "surface": "universal",
            "operator_outstanding": 0
          },
          "price": {
            "credits": 1,
            "charged": true,
            "charged_at": "2026-08-30T09:32:55Z"
          },
          "open": [],
          "conflicts": [],
          "notices": [],
          "filings": [
            {
              "id": "flg_5Tz1",
              "kind": "section_14a",
              "mode": "live",
              "stage": "submitted",
              "stage_label": {
                "de": "Beim Netzbetreiber eingereicht",
                "en": "Filed with the grid operator"
              },
              "operator_ref": null,
              "updated_at": "2026-08-30T09:33:02Z"
            }
          ],
          "last_event_seq": 7
        }
      },
      "FieldDictionarySlice": {
        "summary": "Ein Ausschnitt des Wörterbuchs",
        "value": {
          "version": "3.0",
          "surface": "universal",
          "operator": null,
          "data": [
            {
              "path": "device.grid_draw_kw",
              "nr": 5017,
              "label": {
                "de": "Netzbezugsleistung",
                "en": "Grid draw"
              },
              "help": {
                "de": "Tragen Sie den Wert für die maximale Netzbezugsleistung der Ladeeinrichtung am Netzanschlusspunkt in Kilowatt (kW) ein.",
                "en": "Enter the maximum grid draw of the charge point."
              },
              "type": "number",
              "unit": "kW",
              "required_when": {
                "device.category": [
                  "charge_point"
                ]
              },
              "owed_by": "installer"
            },
            {
              "path": "section_14a.control_responsibility",
              "nr": 2010,
              "label": {
                "de": "Steuerbarkeit",
                "en": "Controllability"
              },
              "help": {
                "de": "Wählen Sie aus, wer mit der Herstellung der Steuerbarkeit beauftragt wird. Hinweis: für die Bereitstellung und Anbindung des Steuergerätes (Steuerbox) an der Übergabestelle Netz zur Kundenanlage ist der Messtellenbetreiber zuständig.",
                "en": "State who is responsible for grid-oriented control."
              },
              "type": "enum",
              "options": [
                {
                  "value": "grid_operator",
                  "label": {
                    "de": "Netzbetreiber",
                    "en": "Grid operator"
                  }
                },
                {
                  "value": "metering_operator",
                  "label": {
                    "de": "Messstellenbetreiber",
                    "en": "Metering point operator"
                  }
                }
              ],
              "required_when": {
                "device.category": [
                  "charge_point",
                  "heat_pump",
                  "air_conditioning"
                ]
              },
              "owed_by": "us"
            }
          ]
        }
      },
      "ProblemValidation": {
        "summary": "Ein Validierungsfehler",
        "value": {
          "type": "https://wisnots.com/problems/validation-failed",
          "title": "The request could not be accepted",
          "status": 422,
          "code": "validation_failed",
          "detail": "One or more declared values were rejected.",
          "instance": "/v1/anmeldungen/14a",
          "errors": [
            {
              "path": "declared.device.grid_draw_kw",
              "code": "out_of_range",
              "message": "Must be greater than 0 and at most 100."
            },
            {
              "path": "declared.section_14a.modules",
              "code": "invalid_combination",
              "message": "Module 2 cannot be combined with module 1."
            }
          ]
        }
      }
    }
  }
}
